Вопрос:

Слияние значений в карте kotlin

java kotlin

1346 просмотра

9 ответа

76 Репутация автора

Мне нужно объединить карты mapAи mapBс парами «имя» - «номер телефона» в окончательную карту, склеив значения дубликатов ключей, разделенных запятыми. Повторяющиеся значения должны быть добавлены только один раз. Мне нужен самый идиоматичный и правильный с точки зрения языкового подхода.

Например:

val mapA = mapOf("Emergency" to "112", "Fire department" to "101", "Police" to "102")
val mapB = mapOf("Emergency" to "911", "Police" to "102")

Конечный результат должен выглядеть так:

{"Emergency" to "112, 911", "Fire department" to "101", "Police" to "102"}

Это моя функция:

fun mergePhoneBooks(mapA: Map<String, String>, mapB: Map<String, String>): Map<String, String> {
    val unionList: MutableMap <String, String> = mapA.toMutableMap()
    unionList.forEach { (key, value) -> TODO() } // here's I can't come on with a beatiful solution

    return unionList
}
Автор: A.Rost Источник Размещён: 17.01.2019 09:16

Ответы (9)


2 плюса

2292 Репутация автора

Я бы написал что-то вроде

fun Map<String, String>.mergeWith(another: Map<String, String>): Map<String, String> {
  val unionList: MutableMap<String, String> = toMutableMap()
  for ((key, value) in another) {
    unionList[key] = listOfNotNull(unionList[key], value).toSet().joinToString(", ")
  }
  return unionList
}

val mergedMap = mapA.mergeWith(mapB)
Автор: Feedforward Размещён: 17.01.2019 09:35

6 плюса

5914 Репутация автора

Решение

Как насчет:

val unionList = (mapA.asSequence() + mapB.asSequence())
    .distinct()
    .groupBy({ it.key }, { it.value })
    .mapValues { (_, values) -> values.joinToString(",") }

Результат:

{Emergency=112,911, Fire department=101, Police=102}

Это будет:

  • создать ленивый Sequenceиз пары ключ-значение обеих карт
  • группировать их по ключевым (результат: Map<String, List<String>)
  • карта их значения запятыми присоединились строки (результат: Map<String, String>)
Автор: Moira Размещён: 17.01.2019 09:38

2 плюса

161 Репутация автора

    val mapA = mapOf("Emergency" to "112", "Fire department" to "101", "Police" to "102")
    val mapB = mapOf("Emergency" to "911", "Police" to "102")

    val result = (mapA.entries + mapB.entries)
        .groupBy({ it.key }, { it.value })
        .mapValues {(_, value) -> 
            value.joinToString(", ")
        }
Автор: Ilya E Размещён: 17.01.2019 09:39

1 плюс

4778 Репутация автора

Другой подход:

val mapA = mapOf("Emergency" to "112", "Fire department" to "101", "Police" to "102")
val mapB = mapOf("Emergency" to "911", "Police" to "102")

val result = mapA.toMutableMap()
mapB.forEach {
    var value = result[it.key]
    value = if (value == null || value == it.value) it.value else value + ", ${it.value}"
    result[it.key] = value
}

Или используя инфиксную функцию расширения :

infix fun Map<String, String>.mergeWith(anotherMap: Map<String, String>): Map<String, String> {
    val result = this.toMutableMap()
    anotherMap.forEach {
        var value = result[it.key]
        value = if (value == null || value == it.value) it.value else value + ", ${it.value}"
        result[it.key] = value
    }
    return result
}

val result = mapA mergeWith mapB
Автор: Sergey Размещён: 17.01.2019 09:48

0 плюса

32659 Репутация автора

Вот мое решение:

val result = (mapA + (mapB - mapA.keys)).mapValues {
    (setOf(it.value) + mapB[it.key]).filterNotNull().joinToString()
}

Он создает карту A плюс значения из B, которых нет в A. Затем он отображает все значения в набор и добавляет значение из B в этот набор, в конечном итоге удаляя все nullзначения из набора и преобразовывая его в список, который Вы можете использовать для создания желаемого формата вывода.

Автор: s1m0nw1 Размещён: 17.01.2019 12:40

1 плюс

11625 Репутация автора

Хотя я смотрел на другие решения, я не мог поверить, что не существует более простого способа (или способов, столь же простых, как принятый ответ, без необходимости воссоздания Map, промежуточных новых списков и т. Д.). Вот 3 (из многих ;-)) решений, которые я придумал:

  1. Используя ключи и сопоставляя значения позже:

    (mapA.keys.asSequence() + mapB.keys)
        .associateWith {
          sequenceOf(mapA[it], mapB[it]) // one of the sides may have null values in it (i.e. no entry in the map)...
              .filterNotNull()
              .distinct()
              .toList() // or if you require/prefer, do the following instead: joinToString()
        }
    
  2. Используя groupingByи fold(или посмотрите на: Сгруппировать по ключу и сверните каждую группу одновременно (KEEP) ):

    (mapA.asSequence() + mapB.asSequence())
      .groupingBy { it.key }
      .fold(mutableSetOf<String>()) { accumulator, element ->
        accumulator.apply {
          add(element.value)
        }
      }
    

    Вы также можете просто использовать пустое Stringвместо этого и объединять в операции сгиба так, как вам нужно. Мой первый подход просто использовал sequenceOfвместо MutableSet. Это зависит от того, что вам нужно и что вы хотите сделать с результатом впоследствии.

  3. Использование Javas Map.merge, но игнорирование дубликатов в значении и просто конкатенация значений:

    val mergedMap: Map<String, String> = mapA.toMutableMap().apply {
      mapB.forEach { key, value ->
        merge(key, value) { currentValue, addedValue ->
          "$currentValue, $addedValue" // just concatenate... no duplicates-check..
        }
      }
    }
    

    Это, конечно, также может быть написано по-другому, но таким образом мы гарантируем, что mergedMap все еще только Map<String, String>когда снова обращаются.

Автор: Roland Размещён: 17.01.2019 05:20

2 плюса

11498 Репутация автора

Вы можете сделать следующее:

(mapA.keys + mapB.keys).associateWith {
    setOf(mapA[it], mapB[it]).filterNotNull().joinToString()
}
  1. положить все ключи в комплекте
  2. перебрать этот набор и связать каждый элемент с набором значений
  3. удалить нулевые значения из набора значений
  4. объединить элементы в списке значений, используя joinToString().
Автор: Willi Mentzel Размещён: 17.01.2019 06:35

2 плюса

100 Репутация автора

В Kotlin вы могли бы сделать это:

fun main() {
    val map1 = mapOf("A" to 1, "B" to 2)
    val map2 = mapOf("A" to 5, "B" to 2)
    val result: Map<String, Int> = listOf(map1, map2)
        .fold(mapOf()) { accMap, map ->
            accMap.merge(map, Int::plus)
        }
    println(result) // Prints: {A=6, B=4}
}

private fun <T, V> Map<T, V>.merge(another: Map<T, V>, mergeFunction: (V, V) -> V): Map<T, V> =
    toMutableMap()
        .apply {
            another.forEach { (key, value) ->
                merge(key, value, mergeFunction)
            }
        }
Автор: Anton Kushch Размещён: 04.06.2019 09:46

0 плюса

15995 Репутация автора

Вот мой подход с универсальной вспомогательной функцией слияния карт:

fun <K, V, R> Pair<Map<K, V>, Map<K, V>>.merge(merger: (V?, V?) -> R): Map<K, R> {
    return (first.keys.asSequence() + second.keys.asSequence())
            .associateWith { merger(first[it], second[it]) }
}

fun main() {
    val mapA = mapOf("Emergency" to "112", "Fire department" to "101", "Police" to "102")
    val mapB = mapOf("Emergency" to "911", "Police" to "102")
    val result = (mapA to mapB).merge { a, b -> 
            listOf(a, b).filterNotNull().distinct().joinToString(",", "(", ")") }
    println(result)
}

Выход:

{Чрезвычайная ситуация = (112 911), Пожарная служба = (101), Полиция = (102)}

Автор: Vadzim Размещён: 12.06.2019 09:24
Вопросы из категории :
32x32