Слияние значений в карте kotlin - PullRequest
0 голосов
/ 17 января 2019

Мне нужно объединить карты 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
}

Ответы [ 9 ]

0 голосов
/ 04 июня 2019

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

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)
            }
        }
0 голосов
/ 13 июня 2019

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

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

0 голосов
/ 17 января 2019

Хотя я смотрел на другие решения, я не мог поверить, что не существует более простого способа (или способов, столь же простых, как принятый ответ, без необходимости воссоздания 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> при повторном доступе.

0 голосов
/ 17 января 2019

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

(mapA.keys + mapB.keys).associateWith {
    setOf(mapA[it], mapB[it]).filterNotNull().joinToString()
}
  1. положить все ключи в комплекте
  2. переберите этот набор и свяжите каждый элемент с набором значений
  3. удалить нулевые значения из набора значений
  4. объединяет элементы в списке значений, используя joinToString().
0 голосов
/ 17 января 2019

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

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

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

0 голосов
/ 17 января 2019

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

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
0 голосов
/ 17 января 2019
    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(", ")
        }
0 голосов
/ 17 января 2019

Как насчет:

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>)
0 голосов
/ 17 января 2019

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

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...