Merge values in map kotlin

前端 未结 11 1397
慢半拍i
慢半拍i 2021-02-18 23:50

I need merge maps mapA andmapB with pairs of \"name\" - \"phone number\" into the final map, sticking together the values for duplicate keys, separated

相关标签:
11条回答
  • 2021-02-18 23:54

    Here is my approach with universal map-merging helper function:

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

    Output:

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

    0 讨论(0)
  • 2021-02-18 23:55
    (mapA.data.asSequence() + mapB.asSequence())
       .map { Pair(it.key, it.value) }
       .toMap()
    
    0 讨论(0)
  • 2021-02-18 23:57

    How about:

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

    Result:

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

    This will:

    • produce a lazy Sequence of both maps' key-value pairs
    • group them by key (result: Map<String, List<String>)
    • map their values to comma-joined strings (result: Map<String, String>)
    0 讨论(0)
  • 2021-02-18 23:57

    While I looked at the other solutions I couldn't believe that there isn't an easier way (or ways as easy as the accepted answer without the need to recreate a Map, intermediate new lists, etc.). Here are 3 (of many ;-)) solutions I came up with:

    1. Using the keys and mapping the values later:

      (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. Using groupingBy and fold (or have a look at: Group by key and fold each group simultaneously (KEEP)):

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

      You could also just use an empty String instead and concatenate in the fold operation the way you need it. My first approach just used a sequenceOf instead of the MutableSet. It depends on what you require and what you want to do with the result afterwards.

    3. Using Javas Map.merge, but ignoring duplicates in the value and also just concatenating the values:

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

      This, of course, can also be written differently, but this way we ensure that mergedMap is still just a Map<String, String> when accessed again.

    0 讨论(0)
  • 2021-02-18 23:57

    In Kotlin you could do this:

    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 讨论(0)
  • 2021-02-18 23:57

    A more generic approach (as this post comes up when searching for kotlin and merging maps):

    fun <K, V1, V2, R> Map<K, V1>.mergeReduce(other: Map<K, V2>, reduce: (key: K, value1: V1?, value2: V2?) -> R): Map<K, R> =
        (this.keys + other.keys).associateWith { reduce(it, this[it], other[it]) }
    

    It allows for Maps with different types of values to be merged, increased freedom with a custom reducer and increased readability.

    Your problem can than be solved as:

    mapA.mergeReduce(mapB) { _, value1, value2 -> listOfNotNull(value1, value2).joinToString(", ") }
    
    0 讨论(0)
提交回复
热议问题