Question:
How to merge the maps while summing up values of common keys among the maps.
Input:
[a: 10, b:2, c:3]
[b:3, c:2, d:5]
Output
[a:10, b:5, c:5, d:5]
Extended Question:
How to merge the original 2 maps, by applying a function (Closure) on the values of the common keys in the 2 maps. i.e.. instead of simply summing up the values of common keys let the user specify the function to use.
For eg: if user wants to use 'min' function instead of summing, then one can specify min to get [a:10, b:2, c:2, d:5]
as the result.
The easiest way to merge two maps in Groovy is to use + operator. This method is straightforward - it creates a new map from the left-hand-side and right-hand-side maps.
concat() Alternatively, we can use Stream#concat() function to merge the maps together. This function can combine two different streams into one. As shown in the snippet, we are passed the streams of map1 and map2 to the concate() function and then collected the stream of their combined entry elements.
Combine lists using the plus operator The plus operator will return a new list containing all the elements of the two lists while and the addAll method appends the elements of the second list to the end of the first one.
Assuming that both maps contain the same set of keys, and that you want to "combine" the values, the thing you would be looking for is a Pair class, see here for example. You simply iterate one of the maps; and retrieve values from both maps; and create a Pair; and push that in your result map.
Let's start by defining a map: We can add a key to the map: But another more Javascript-like way is using property notation (the dot operator): In other words, Groovy supports accessing of key-value pairs in a bean like fashion. We can also use variables instead of literals as keys while adding new items to the map:
How to merge the maps while summing up values of common keys among the maps. How to merge the original 2 maps, by applying a function (Closure) on the values of the common keys in the 2 maps. i.e.. instead of simply summing up the values of common keys let the user specify the function to use.
1. Overview Groovy extends the Map API in Java to provide methods for operations such as filtering, searching and sorting. It also provides a variety of shorthand ways of creating and manipulating maps. In this article, we'll look at the Groovy way of working with maps.
The groupBy () method returns a map of maps. And each map contains key-value pairs which evaluate to the same result for the given condition: Another way of creating submaps is by using subMap ().
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.
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)
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