Reputation: 9700
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.
Upvotes: 10
Views: 4117
Reputation: 1139
Other answers work but they're specific to using MutableList
as value and are not concise enough for me, here's a solution exactly similar to python's defaultdict
.
define this class (not a kotlin expert so improvements are welcome):
class defaultMutableMapOf<T, U>(
private val map: MutableMap<T, U> = mutableMapOf(),
private val defaultValue: () -> U,
) : MutableMap<T, U> by map {
override fun get(key: T): U = if (key in map) {
map[key]!!
} else {
map[key] = defaultValue.invoke()
map[key]!!
}
}
Usage:
val map1 = defaultMutableMapOf<String, Int> { 0 }
map1["hello"]++
map1["world"] += 2
// map1 = {"hello":1,"world":2}
val map2 = defaultMutableMapOf<String, MutableList<String>> { mutableListOf() }
map2["categories"].addAll(listOf("A", "B", "C"))
map2["items"] += "item1"
// map2 = {"categories":["A","B","C"],"items":["item1"]}
I don't know why Kotlin doesn't have something like this by default
Upvotes: 0
Reputation: 177
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)
}
}
Upvotes: 0
Reputation: 81959
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)
}
Upvotes: 3
Reputation: 4951
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)
Upvotes: 1