Cybran
Cybran

Reputation: 2313

Kotlin: Retain, replace or remove each map entry in-place

I have a mutable map (a LinkedHashMap to be specific) in Kotlin. For each map entry, I want to conditionally perform one of three actions:

In old Java, I would probably use an Iterator over the map's entrySet() for this purpose (using Iterator.remove() or Entry.setValue() accordingly). However, I wondered if there is a more elegant, functional approach present in Kotlin to do the same (i.e. apply a lambda to each entry in-place, similar to using replaceAll(), but with the ability to also remove entries).


In Kotlin, the following combination of replaceAll() and retainAll() does the same thing:

val map = LinkedHashMap<String,Int>(mapOf("a" to 1, "b" to 2, "c" to 3))
map.replaceAll { key, value ->
    if(key.startsWith("a")) {
        value * 2
    } else {
        value
    }
}
map.entries.retainAll { entry ->
    entry.key.startsWith("a") || entry.key.startsWith("b")
}

However, this iterates the map twice and requires splitting up the predicate/transformation. I would prefer a compute()-style all-in-one solution, just for all entries.

Upvotes: 12

Views: 16333

Answers (4)

Fartab
Fartab

Reputation: 5513

In functional world, immutability is an essential principle. So in-place modifications are not convenient.

But if you prefer an in-place approach, possible options are retainAll or removeAll to filter items. For in-place replacement you need to iterate the collection. You can use forEach to do an in-place modification:

data class User(var name: String)

val users = mutableListOf( User("John"), User("Alice"), User("Bob") )
users.run {
    removeAll { it.name == "Alice" }
    forEach { it.name =  "Mr. ${it.name}"}
} 

Upvotes: 4

Balaban Mario
Balaban Mario

Reputation: 461

This version is in place (similar to what would you write in java) but not with functional approach

 val map: MutableMap<String, Int> = mutableMapOf(
    ("a" to 1),
    ("b" to 2),
    ("c" to 3),
)

val mapIterator = map.iterator()
while (mapIterator.hasNext()) {
    val mapEntry = mapIterator.next()

    when (mapEntry.key) {
        "a" -> mapIterator.remove()
        "c" -> mapEntry.setValue(mapEntry.value * 2)
        else -> println("retain this  entry")
    }
}

Upvotes: 2

mentallurg
mentallurg

Reputation: 5207

Here is an example how you can do all operations in a single expression:

  • Using filter{} we keep only elements that fit particular conditions, here: with a value >= 200. The result of this filter is a new map.
  • Using map we transform a map from previous step into a new collection. When transforming, we change some values, some remain unchanged.
  • Using toMap() we transform the resulting collection into a new map.
fun main(args: Array<String>) {
    val map = mutableMapOf<String, Int>()

    map.put("a1", 101)
    map.put("a2", 102)

    map.put("b1", 201)
    map.put("b2", 202)

    map.put("c1", 301)
    map.put("c2", 302)

    println(map)

    val result = map.filter { (key, value) -> value >= 200 }.
        map { (key,value) -> 
                if (value >= 300)   Pair(key, value * 100) 
                else                Pair(key, value)
    }.toMap()

    println(result)
}

Upvotes: 4

Andrei Tanana
Andrei Tanana

Reputation: 8442

For example, you can write like this

fun main() {
    val map = mapOf(
        "test1" to 1,
        "test2" to 2,
        "test3" to 3
    )
    val resultMap = map.mapNotNull { (key, value) ->
        when (value) {
            1 -> null // remove
            2 -> key to 4 // replace
            else -> key to value // retain
        }
    }.toMap()
    println(resultMap) // print {test2=4, test3=3}
}

Upvotes: 2

Related Questions