#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 нет чего-то подобного по умолчанию