Kotlin-idiomatic way to add an item to a list in a map

Viewed 1344

I have a MutableMap<String, MutableList<String> I'm adding items to, basically a collection where the same key is associated to several values.

Whenever I want to add a new value, I need to check first if there already is a list associated to the same key, initialize one if there isn't, and then add the value to the list.

I can do this in a pretty verbose way by doing

if (map.containsKey(key)) {
    map[key].add(value)
} else {
    map[key] = mutableListOf(value)
}

I also can do this in a very concise way by doing

map[key] = (map[key] ?: mutableListOf()) + mutableListOf(value)).toMutableList()

And in several other ways in between in terms of verbosity vs. conciseness.

What is the idiomatic way to do this in Kotlin, though?

I'm not really aiming for conciseness but for a form that is immediately recognizable and understandable.

4 Answers

You can use getOrPut

map.getOrPut(key) { mutableListOf() }.add(value)

I had to think of Python as it provides a defaultdict that can be used like this:

from collections import defaultdict 

data = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 5)]
d = defaultdict(set)
for k, v in data:
    d[k].add(v)

print(d.items()) # dict_items([('red', {1, 3}), ('blue', {2, 4, 5})])

Interestingly, Kotlin comes with something similar built-in, too, which is the withDefault extension:

val data = listOf("red" to 1, "blue" to 2, "red" to 3, "blue" to 4, "red" to 1, "blue" to 5)
val map = mutableMapOf<String, Set<Int>>().withDefault { emptySet() }
for (d in data) {
    map[d.first] = (map.getValue(d.first) + d.second)
}

Create an extension function (name it so "that it is immediately recognizable and understandable"):

fun MutableMap<String, MutableList<String>>.addValue(key: String, value: String) {
    if (containsKey(key)) {
        this[key]?.add(value)
    } else {
        this[key] = mutableListOf(value)
    }
}

Use the extension function wherever required:

map.addValue(key, value)

This is a generic version of the answer @Xid provided that will work regardless of the type of the key and value

fun <K, V> MutableMap<K, MutableList<V>>.addValue(key: K, value: V) {
    if (containsKey(key)) {
        this[key]?.add(value)
    } else {
        this[key] = mutableListOf(value)
    }
}
Related