Raibaz
Raibaz

Reputation: 9700

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

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

Answers (5)

yazan sayed
yazan sayed

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

WoodyM
WoodyM

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

s1m0nw1
s1m0nw1

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

Xid
Xid

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

IR42
IR42

Reputation: 9692

You can use getOrPut

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

Upvotes: 20

Related Questions