Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge two maps in groovy

Tags:

groovy

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.

like image 570
Don Avatar asked Oct 26 '16 16:10

Don


People also ask

How do I merge two Groovy maps?

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.

How can I merge two 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.

How do I merge two lists in Groovy?

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.

How can I combine two hashmap objects containing the different types?

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.

How to add a key to a map in Groovy?

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 maps while summing up common keys?

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.

What is groovy 1 1?

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.

What is the difference between groupby () and submap?

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 ().


2 Answers

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.

like image 54
Nathan Hughes Avatar answered Dec 12 '22 06:12

Nathan Hughes


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) 
like image 42
Rao Avatar answered Dec 12 '22 06:12

Rao