I need merge maps mapA
andmapB
with pairs of \"name\" - \"phone number\" into the final map, sticking together the values for duplicate keys, separated
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)}
(mapA.data.asSequence() + mapB.asSequence())
.map { Pair(it.key, it.value) }
.toMap()
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:
Sequence
of both maps' key-value pairsMap<String, List<String>
)Map<String, String>
)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:
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()
}
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.
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.
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)
}
}
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(", ") }