xX_Xeno_Blade_Xx
xX_Xeno_Blade_Xx

Reputation: 51

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

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" }

Upvotes: 1

Views: 917

Answers (4)

Arpit Shukla
Arpit Shukla

Reputation: 10493

I wan to have my own update method how can I do that. I expect something liek this: dogs.update { dog -> dog == "shiba" } { "doge" }

If 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.

Upvotes: 1

Sweeper
Sweeper

Reputation: 272750

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.

Upvotes: 1

Tenfour04
Tenfour04

Reputation: 93759

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")

Upvotes: 0

Stefan Zhelyazkov
Stefan Zhelyazkov

Reputation: 2981

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]

}

Upvotes: 0

Related Questions