Kotlin-идиоматический способ добавления элемента в список на карте

#kotlin #collections #idioms

#kotlin #Коллекции #идиомы

Вопрос:

У меня есть MutableMap<String, MutableList<String> , в который я добавляю элементы, в основном коллекция, в которой один и тот же ключ связан с несколькими значениями.

Всякий раз, когда я хочу добавить новое значение, мне нужно сначала проверить, есть ли уже список, связанный с тем же ключом, инициализировать его, если его нет, а затем добавить значение в список.

Я могу сделать это довольно подробно, выполнив

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

Я также могу сделать это очень кратко, выполнив

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

И несколькими другими промежуточными способами с точки зрения подробностей и краткости.

Какой идиоматический способ сделать это в Kotlin?

На самом деле я стремлюсь не к лаконичности, а к форме, которая сразу узнаваема и понятна.

Ответ №1:

Вы можете использовать getOrPut

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

Комментарии:

1. это, безусловно, самый простой и наиболее идиоматичный способ сделать это. Нет причин изобретать велосипед, как в других ответах.

2. или еще более идиоматичный map.getOrPut(key) { mutableListOf() } = value

Ответ №2:

Мне пришлось подумать о Python, поскольку он предоставляет a defaultdict , который можно использовать следующим образом:

 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})])
  

Интересно, что Kotlin также имеет нечто похожее встроенное, которое является withDefault расширением:

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

Ответ №3:

Создайте функцию расширения (назовите ее так, «чтобы она была сразу узнаваемой и понятной»):

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

Используйте функцию расширения везде, где требуется:

 map.addValue(key, value)
  

Ответ №4:

Это общая версия ответа @Xid при условии, что он будет работать независимо от типа ключа и значения

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

Ответ №5:

Другие ответы работают, но они специфичны для использования MutableList в качестве значения и недостаточно кратки для меня, вот решение, в точности похожее на python defaultdict .

определите этот класс (не эксперт kotlin, поэтому улучшения приветствуются):

 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]!!
  }
}
  

Использование:

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

Я не знаю, почему у Kotlin нет чего-то подобного по умолчанию