Home > OS >  Kotlin, How to create extension function for updating element in the list
Kotlin, How to create extension function for updating element in the list

Time:07-22

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.

  • Related