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
}
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.
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.
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>
)You can do the following:
(mapA.keys + mapB.keys).associateWith {
setOf(mapA[it], mapB[it]).filterNotNull().joinToString()
}
joinToString()
.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() }) { _, 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.
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)
}
}
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)}
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)
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(", ")
}
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(", ") }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With