How to merge two maps in groovy - 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.

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)

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]

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]

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]

Related

groovy map populate with default element

Is there more Groovish way of adding an element to map of lists and initialize default list if not exists?
Or in other words what would be a Groovish way to code the below:
def mylist = [1,2,3,4]
def mymap = [:]
for (num in mylist){
if (num % 2 == 0){
pairity = "even"
} else {
pairity = "odd"
}
if (mymap.containsKey(pairity)){
println("Adding to Even")
mymap[pairity].add(num)
}
else {
println("adding to Odd")
mymap[pairity] = [num]
}
}
print(mymap.toString())
// adding to Odd
// adding to Odd
// Adding to Even
// Adding to Even
// [odd:[1, 3], even:[2, 4]]
You can use withDefault on a map to have automatically generate a value for a missing key on access.
[1,2,3,4].inject([:].withDefault{[]}){ m, i -> m[ i%2==0 ? 'odd' : 'even' ] << i; m }
// => [even:[1, 3], odd:[2, 4]]
You can simply groupby:
def mymap = mylist.groupBy { it % 2 == 0 ? 'even' : 'odd' }
That is effectively using the closure to partition the list on the condition.

Nested comprehension in Kotlin

Suppose I have the following nested for loop:
val test = mutableSetOf<Set<Int>>()
for (a in setA) {
for (b in setB) {
if (a.toString().slice(2..3) == b.toString().slice(0..1)) {
test.add(setOf(a,b))
}
}
}
In python, I could do a simple comprehension as
test = {[a,b] for a in setA for b in setB if a.str()[2:3] == b.str[0:1]}
I'm having a helluva time converting this to Kotlin syntax. I know for a single for loop with a conditional, I could use a filter and map to get the desired results (using the idiom: newSet = oldSet.filter{ conditional }.map { it }, but I cannot for the life of me figure out how to do the nesting this way.
This is what IDEA proposes:
for (a in setA)
setB
.filter { a.toString().slice(2..3) == it.toString().slice(0..1) }
.mapTo(test) { setOf(a, it) }
I do not think there is much to do about it. I think their is no native approach that is similar to the Python one, but it already actually is in terms of length very similar because only the functions and their names make it that long.
If we take a look a this hypothetical example:
for (a in setA) setB.f { a.t().s(2..3) == it.t().s(0..1) }.m(test) { setOf(a, it) }
It is not far from the Python example. The Python syntax is just very different.
(functions for that hypothesis)
fun <T> Iterable<T>.f(predicate: (T) -> Boolean) = filter(predicate)
fun String.s(range: IntRange) = slice(range)
fun <T, R, C : MutableCollection<in R>> Iterable<T>.m(destination: C, transform: (T) -> R) = mapTo(destination, transform)
fun Int.t() = toString()
If Kotlin doesn't have it, add it. Here is a cartesian product of the two sets as a sequence:
fun <F,S> Collection<F>.cartesian(other: Collection<S>): Sequence<Pair<F,S>> =
this.asSequence().map { f -> other.asSequence().map { s-> f to s } }.flatten()
Then use that in one of many ways:
// close to your original nested loop version:
setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.forEach{ (a,b) -> test.add(setOf(a,b)) }
// or, add the pair instead of a set if that makes sense as alternative
setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.forEach{ test2.add(it) }
// or, add the results of the full expression to the set at once
test.addAll(setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
}.map { (a,b) -> setOf(a,b) } )
// or, the same as the last using a pair instead of 2 member set
test2.addAll(setA.cartesian(setB).filter { (a,b) ->
a.toString().slice(2..3) == b.toString().slice(0..1)
})
The above examples use these variables:
val test = mutableSetOf<Set<Int>>()
val test2 = mutableSetOf<Pair<Int,Int>>()
val setA = setOf<Int>()
val setB = setOf<Int>()

I need a shorthand way to combine Maps in groovy with slightly different results than +

NOTE: This can be done as a method call or an operator override pretty easily, I am looking for an intrinsic one-line solution that I don't have to carry around in a library.
When you combine(add) Maps, you get a result like this:
println [a:1,c:3] + [a:2]
// prints {a=2, c=3}
I seem to keep needing results more like:
{a=[1, 2], c=[3]}
In other words, something that combines all the values from identical keys in the Maps.
Is there an operator or simple function call that does this, because doing it myself always seems to break my stride a little. It seems like the * operator might do this nicely, but it doesn't.
Is there an easy way to do this?
Another alternative (adding it to the * operator on Maps)
def a = [ a:1, c:10 ]
def b = [ b:1, a:3 ]
Map.metaClass.multiply = { Map other ->
(delegate.keySet() + other.keySet()).inject( [:].withDefault { [] } ) { m, v ->
if (delegate[v] != null) { m[v] << delegate[v] }
if (other[v] != null) { m[v] << other[v] }
m
}
}
assert a * b == [a:[1, 3], c:[10], b:[1]]
Came up with this as well, but it's late and there are probably better, shorter ways
def a = [ a:1, c:10 ]
def b = [ b:1, a:3 ]
[a,b]*.collect {k,v -> [(k):v]}
.flatten()
.groupBy { it.keySet()[0]}
.inject([:].withDefault{[]}) {m,v->
m << [(v.key):v.value[v.key]]
}
​​
Nothing came to my mind, so I start the bidding with this:
m1 = [a:1, c:666]; m2 = [a:2, b:42]
result = [:].withDefault{[]}
[m1,m2].each{ it.each{ result[it.key] << it.value } }
assert result == [a:[1,2], b:[42], c:[666]]

Update map using findAll and each in groovy

I would like to update values in map in Groovy filling certain criteria. Here is my code:
def m = [:]
m['a'] = 1
m['b'] = 2
m['d'] = 3
m.findAll { it.value > 1}.each {
it.value = 4
}
println m
But the result is following:
[a:1, b:2, d:3]
Is there any way to do it using both findAll and each? Or I must use
m.each {if (it.value>1) it.value=4}
The root cause is findAll returns a new Map instance.
So you could try:
newMap = m.findAll { it.value > 1}.each {
it.value = 4
}
println m //No change
println newMap //This is what you need!
output is
[a:1, b:2, d:3]
[b:4, d:4]
In each case, the values you are iterating with the each are map entries with a pointer to key and value. When you set it.value you are not replacing what is in the map. You are only updating the pointer in the map entry. To actually set the value, you will need to do the following:
m.findAll { it.value > 1 }.each { m[it.key] = 4 }

Groovier way of manipulating the list

I have two list like this :
def a = [100,200,300]
def b = [30,60,90]
I want the Groovier way of manipulating the a like this :
1) First element of a should be changed to a[0]-2*b[0]
2)Second element of a should be changed to a[1]-4*b[1]
3)Third element of a should be changed to a[2]-8*b[2]
(provided that both a and b will be of same length of 3)
If the list changed to map like this, lets say:
def a1 = [100:30, 200:60, 300:90]
how one could do the same above operation in this case.
Thanks in advance.
For List, I'd go with:
def result = []
a.eachWithIndex{ item, index ->
result << item - ((2**index) * b[index])
}
For Map it's a bit easier, but still requires an external state:
int i = 1
def result = a.collect { k, v -> k - ((2**i++) * v) }
A pity, Groovy doesn't have an analog for zip, in this case - something like zipWithIndex or collectWithIndex.
Using collect
In response to Victor in the comments, you can do this using a collect
def a = [100,200,300]
def b = [30,60,90]
// Introduce a list `c` of the multiplier
def c = (1..a.size()).collect { 2**it }
// Transpose these lists together, and calculate
[a,b,c].transpose().collect { x, y, z ->
x - y * z
}
Using inject
You can also use inject, passing in a map of multiplier and result, then fetching the result out at the end:
def result = [a,b].transpose().inject( [ mult:2, result:[] ] ) { acc, vals ->
acc.result << vals.with { av, bv -> av - ( acc.mult * bv ) }
acc.mult *= 2
acc
}.result
And similarly, you can use inject for the map:
def result = a1.inject( [ mult:2, result:[] ] ) { acc, key, val ->
acc.result << key - ( acc.mult * val )
acc.mult *= 2
acc
}.result
Using inject has the advantage that you don't need external variables declared, but has the disadvantage of being harder to read the code (and as Victor points out in the comments, this makes static analysis of the code hard to impossible for IDEs and groovypp)
def a1 = [100:30, 200:60, 300:90]
a1.eachWithIndex{item,index ->
println item.key-((2**(index+1))*item.value)
i++
}

Resources