Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge values in map kotlin

Tags:

java

kotlin

I need merge maps mapA andmapB with pairs of "name" - "phone number" into the final map, sticking together the values for duplicate keys, separated by commas. Duplicate values should be added only once. I need the most idiomatic and correct in terms of language approach.

For example:

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

The final result should look like this:

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

This is my function:

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
}
like image 491
rost Avatar asked Jan 17 '19 09:01

rost


People also ask

What is flatMap Kotlin?

Syntax of Kotlin flatMap The flatMap() is one of the methods that is used to create the single set of list entries from the input results. fun main(args:Array<String>) { val varss= listOf(“”) // Instead of listOf() method we can use any other collection methods like hashmap etc.

How do you use the map on Kotlin?

Android Dependency Injection using Dagger with KotlinKotlin map is a collection of key/value pairs, where each key is unique, and it can only be associated with one value. The same value can be associated with multiple keys though. We can declare the keys and values to be any type; there are no restrictions.


9 Answers

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>)
like image 91
Salem Avatar answered Sep 27 '22 23:09

Salem


You can do the following:

(mapA.keys + mapB.keys).associateWith {
    setOf(mapA[it], mapB[it]).filterNotNull().joinToString()
}
  1. put all keys in a set
  2. iterate over that set and and associate each element with the set of values
  3. remove the null values from the value set
  4. concatenate the elements in the value list using joinToString().
like image 28
Willi Mentzel Avatar answered Sep 24 '22 23:09

Willi Mentzel


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() }) { _, 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. Be sure to use the overloaded fold-function that accepts an initial value selector, which creates a new initial value every time a new key is encountered. Thanks xzt for noting it.

  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.

like image 41
Roland Avatar answered Sep 23 '22 23:09

Roland


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)
            }
        }
like image 43
Anton Kushch Avatar answered Sep 26 '22 23:09

Anton Kushch


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

like image 26
Vadzim Avatar answered Sep 23 '22 23:09

Vadzim


I would write something like

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)
like image 30
Feedforward Avatar answered Sep 26 '22 23:09

Feedforward


    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(", ")
        }
like image 30
Ilya E Avatar answered Sep 27 '22 23:09

Ilya E


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(", ") }
like image 31
Wouter Oet Avatar answered Sep 23 '22 23:09

Wouter Oet


Another approach:

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
}

Or using infix extension function:

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
like image 27
Sergey Avatar answered Sep 27 '22 23:09

Sergey