I want to update element in a list, but since list doesn't have update method so I want to do something like this.
val dogs = listOf("shiba", "golden", "samoid")
// update if dog is shiba, otherwise remain the same value.
dogs.map { dog ->
if(dog == "shiba") { "doge" }
else { dog }
}
It works but ugly and I don't like it. I have seen other people create an extension function and it's so cool.
fun <T> MutableList<T>.mapInPlace(mutator: (T) -> (T)) {
this.forEachIndexed { i, value ->
val changedValue = mutator(value)
if (value != changedValue) {
this[i] = changedValue
}
}
}
// now I can use
dogs.mapInPlace { xxx }
I wan to have my own update method how can I do that. I expect something liek this
dogs.update { dog -> dog == "shiba" } { "doge" }
CodePudding user response:
You can do it as you have started to describe it: https://pl.kotl.in/lQAb0rAmm
fun <T> MutableList<T>.update(mutator: (T) -> (T)) {
this.forEachIndexed { i, value ->
val changedValue = mutator(value)
if (value != changedValue) {
this[i] = changedValue
}
}
}
fun main() {
val dogs = mutableListOf("shiba", "golden", "samoid")
dogs.update { if (it == "shiba") "doge" else it }
println(dogs) // [doge, golden, samoid]
}
CodePudding user response:
I think what you want is a function that replaces a value with another value with concise code at the use site?
For a read-only list, you must return a new list. For a mutable list you can replace the value in the existing list.
fun <T> List<T>.replaced(oldValue: T, newValue: T) =
map { if (it == oldValue) newValue else oldValue }
fun <T> MutableList<T>.replace(oldValue: T, newValue: T) {
for (i in indices) {
if(this[i] == oldValue) this[i] = newValue
}
}
Usage:
var readOnlyDogs = listOf("shiba", "golden", "samoid")
readOnlyDogs = readOnlyDogs.replaced("shiba", "doge")
val mutableDogs = mutableListOf("shiba", "golden", "samoid")
mutableDogs.replace("shiba", "doge")
CodePudding user response:
I wan to have my own update method how can I do that. I expect something liek this:
dogs.update { dog -> dog == "shiba" } { "doge" }
I you want to pass a lambda to update
function, you can do the following:
fun <T> List<T>.updateIf(condition: (T) -> Boolean, newValue: T): List<T> {
return this.map {
if (condition(it)) newValue else it
}
}
Usage:
val dogs = listOf("shiba", "golden", "samoid")
println(dogs.updateIf({ dog -> dog == "shiba" }, "doge"))
You can play with the arguments a little bit if you want to. For example, if you want to pass the newValue
as a lambda only, you can change it like this:
fun <T> List<T>.updateIf(condition: (T) -> Boolean, newValue: (T) -> T): List<T> {
return this.map {
if (condition(it)) newValue(it) else it
}
}
Usage:
println(dogs.updateIf({ dog -> dog == "shiba" }, {"doge"}))
// OR using the trailing lambda syntax,
println(dogs.updateIf({ dog -> dog == "shiba" }) {"doge"})
Note that you can't do:
dogs.update { dog -> dog == "shiba" } { "doge" }
because Kotlin allows you to pass only the last lambda argument as a trailing lambda.
CodePudding user response:
It is possible to move the if
statement into the extension function too, and achieve a syntax like this:
val dogs = mutableListOf("shiba", "golden", "samoid")
dogs.update({ it == "shiba" }) { "doge" }
println(dogs) // [doge, golden, samoid]
You can declare update
like this:
fun <T> MutableList<T>.update(condition: (T) -> Boolean, value: (T) -> T) {
for (i in indices) {
if (condition(this[i])) {
this[i] = value(this[i])
}
}
}
However, I find this not very readable. In this case, a more readable signature in my opinion would be:
fun <T> MutableList<T>.`if`(condition: (T) -> Boolean, setTo: (T) -> T) {
for (i in indices) {
if (condition(this[i])) {
this[i] = setTo(this[i])
}
}
}
// Usage:
dogs.`if`({ it == "shiba" }, setTo = { "doge" })
Since you do not make use of the existing value in the list to compute the new value, it might be even better to change setTo
's type to just T
.