Question:
How to merge the maps while summing up values of common keys among the maps.
Input:
[a: 10, b:2, c:
Below groovy script uses and addresses the OP question using closure. That will help to decide user to choose the merge strategy
for the value of each key in the merged map.
NOTE: The script sample is using 3 maps to make sure the script is able to handle the merging of multiple maps. This solution provided here would scale even if there are more maps to be handled.
While merging, it is possible that each map may not have all the keys, so it is possible to have null
when user tries to get the value. Hence removing null
from list that is passed to the Collection
.
/**
* this script to merge the maps based on the closure provided by user based on different use case
*/
//For sample, taking below 3 maps
def map1 = [a:10, b:2, c:3]
def map2 = [b:3, c:2, d:5]
def map3 = [d:3,a:4,e:9]
//Below method takes list of maps and closure as input and returns merged map
def getMergedMap(list, closure) {
def keys = [] as Set
list.each { element -> keys.addAll(element.keySet()) }
def map = [:]
keys.each { k ->
def items = []
list.each { items.add(it[k]) }
map[k] = closure(items)
}
map
}
//Create the list of maps
def mapList = [map1, map2, map3]
//Call the above method and pass the closure are need for merging condition, here min of matched key values from multiple maps
def newmap = getMergedMap(mapList) { list -> Collections.min(list - null) }
println newmap
//Call the above method and pass the closure are need for merging condition, here max of matched key values from multiple maps
newmap = getMergedMap(mapList) { list -> Collections.max(list - null) }
println newmap
//Call the above method and pass the closure are need for merging condition, here sum of matched key values from multiple maps
newmap = getMergedMap(mapList) { list -> (list-null).sum() }
println newmap
Output for the above code:
[a:4, b:2, c:2, d:3, e:9]
[a:10, b:3, c:3, d:5, e:9]
[a:14, b:5, c:5, d:8, e:9]
UPDATE: If you want default behavior while merging, retains value from last map in the order of merging, below closure call can be used
newmap = getMergedMap(mapList) { list -> (list-null).last() }
println newmap
And results to:
[a:4, b:3, c:2, d:3, e:9]
You may quickly test the script from here Demo
UPDATE2:
The above getMeredMap
is simple and readable. Of course, can be groovified / condensed using multiple inject
's to as shown below on-liner:
def getNewMap(list, closure) {
list.inject([], { klist, map -> klist.addAll(map.keySet()); klist as Set }).inject([:]) { m, k -> m[k] = closure(list.inject([]){ vlist,map -> vlist << map[k] });m }
}
UPDATE 3
You may also simplify calling code by defining the closures separately for merged value strategy. That makes little simplify, imo. Also handled null values while merging inside instead of letting user handle outside and this would more clean to those who uses getMergedMap
method.
//Merging of multiple maps with different merge strategies
//And handled null inside of mergeMethod instead of outside like earlier
def map1 = [a:10, b:2, c:3]
def map2 = [b:3, c:2, d:5]
def map3 = [d:3, a:4, e:9]
//Input map list and Merge strategy closure and handling null
def getMergedMap(list, closure) {
list.inject([],{ klist, map -> klist.addAll(map.keySet());klist as Set}).inject([:]) { m, k -> m[k] = closure(list.inject([]){ vlist,map -> vlist << map[k];vlist-null });m }
}
def mapList = [map1, map2, map3]
//Closures for merged value strategy
def minValue = { list -> Collections.min(list) }
def maxValue = { list -> Collections.max(list) }
def totalValue = { list -> list.sum() }
def defaultValue = { list -> list.last() }
//Call merge maps with strategies and assert
assert [a:4, b:2, c:2, d:3, e:9] == getMergedMap(mapList, minValue)
assert [a:10, b:3, c:3, d:5, e:9] == getMergedMap(mapList, maxValue)
assert [a:14, b:5, c:5, d:8, e:9] == getMergedMap(mapList, totalValue)
assert [a:4, b:3, c:2, d:3, e:9] == getMergedMap(mapList, defaultValue)
The first one can be accomplished by:
/* Transform entries in map z by adding values of keys also present in zz
* Take any entries in map zz whose keys are not in z. Add the result.
*/
Map mergeMaps(Map z, Map zz){
Map y = z.inject([:]) { result, e -> zz.keySet().contains(e.key) ? result << [(e.key) : e.value + zz[e.key]] : result << e }
Map yy = zz.findAll { e -> !z.keySet().contains(e.key) }
y + yy
}
Let's use this now at the Groovy console:
mergeMaps([a: 10, b:2, c:3], [b:3, c:2, d:5])
Result: [a:10, b:5, c:5, d:5]
The extended question (more generic) one can be accomplished by a small tweak:
Map mergeMapsWith(Map z, Map zz, Closure cls){
Map y = z.inject([:]) { result, e -> zz.keySet().contains(e.key) ? result << [(e.key) : cls.call(e.value,zz[e.key])] : result << e }
Map yy = zz.findAll { e -> !z.keySet().contains(e.key) }
y + yy
}
Let's use this now at the Groovy console:
mergeMapsWith([a: 10, b:2, c:3], [b:3, c:2, d:5]) { a, b -> Math.min(a,b)}
Result: [a:10, b:2, c:2, d:5]
or if we wanted to merge with a multiplication:
mergeMapsWith([a: 10, b:2, c:3], [b:3, c:2, d:5]) { a, b -> a * b }
Result: [a:10, b:6, c:6, d:5]
Would this suffice?
Map one = [a:10, b:2, c:3]
Map two = [b:3, c:2, d:5]
Map mergeOn(Map one, Map two, Closure closure) {
two.inject([:] << one) { acc, key, val ->
key in acc.keySet() ? acc[key] = closure(acc[key], val) : acc << [(key): val]
acc
}
}
assert mergeOn(one, two) { a, b -> a + b } == [a:10, b:5, c:5, d:5]
assert mergeOn(one, two) { a, b -> a - b } == [a:10, b:-1, c:1, d:5]
assert mergeOn(one, two) { a, b -> a * b } == [a:10, b:6, c:6, d:5]
assert mergeOn(one, two) { a, b -> Math.max(a, b) } == [a:10, b:3, c:3, d:5]
assert mergeOn(one, two) { a, b -> Math.min(a, b) } == [a:10, b:2, c:2, d:5]
You could use inject with ?: for when the map's value for the key is null:
map1 = [a:10, b:2, c:3]
map2 = [b:3, c:2, d:5]
(map1.keySet() + map2.keySet())
.inject([:]) {m, k -> m[k] = (map1[k] ?: 0) + (map2[k] ?: 0); m }
which evaluates to
[a:10, b:5, c:5, d:5]
Alternatively you can use collectEntries (the closure is not as ugly this way):
map1 = [a:10, b:2, c:3]
map2 = [b:3, c:2, d:5]
(map1.keySet() + map2.keySet())
.collectEntries {[(it) : (map1[it] ?: 0) + (map2[it] ?: 0)]}
To make this generic, allow passing in a closure. But collectEntries already allows that, you don't gain much.
Here is a simple solution that collects the unique keys, the values for each key as an array, and applies the lambda to the array of values for each key. In this first one the lambda takes an array:
def process(def myMaps, Closure myLambda) {
return myMaps.sum { it.keySet() }.collectEntries { key ->
[key, myLambda(myMaps.findResults { it[key] })]
}
}
def map1 = [a: 10, b:2, c:3]
def map2 = [b:3, c:2, d:5]
def maps = [map1, map2]
def sumResult = process(maps) { x -> x.sum() }
def prodResult = process(maps) { x -> x.inject(1) { a, b -> a * b } }
def minResult = process(maps) { x -> x.inject(x[0]) { a, b -> a < b ? a : b } }
assert sumResult == [a:10, b:5, c:5, d:5]
assert prodResult == [a:10, b:6, c:6, d:5]
assert minResult == [a:10, b:2, c:2, d:5]
In this 2nd version the lambda expression takes two values:
def process(def myMaps, Closure myLambda) {
return myMaps.sum { it.keySet() }.collectEntries { key ->
[key, { x ->
x.subList(1, x.size()).inject(x[0], myLambda)
}(myMaps.findResults { it[key] })]
}
}
def map1 = [a: 10, b:2, c:3]
def map2 = [b:3, c:2, d:5]
def maps = [map1, map2]
def sumResult = process(maps) { a, b -> a + b }
def prodResult = process(maps) { a, b -> a * b }
def minResult = process(maps) { a, b -> a < b ? a : b }
assert sumResult == [a:10, b:5, c:5, d:5]
assert prodResult == [a:10, b:6, c:6, d:5]
assert minResult == [a:10, b:2, c:2, d:5]