如何在groovy中合并两个地图

时间:2016-10-26 16:29:08

标签: groovy

问题:
如何合并地图,同时总结地图中公共密钥的值。

输入:

[a: 10, b:2, c:3]  
[b:3, c:2, d:5] 

输出

[a:10, b:5, c:5, d:5]

扩展问题:
如何通过对2个映射中的公共键的值应用函数(Closure)来合并原始的2个映射。即..而不是简单地总结公共密钥的值,让用户指定要使用的功能。

例如:如果用户想要使用' min'函数而不是求和,然后可以指定min来得到[a:10, b:2, c:2, d:5]作为结果。

5 个答案:

答案 0 :(得分:4)

当key的映射值为null时,可以使用inject with?:

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 }

评估为

[a:10, b:5, c:5, d:5]

或者你可以使用collectEntries(这样的闭包不是那么难看):

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)]}

为了使这个通用,允许传入一个闭包。但是,collectEntries已经允许这样做,你不会获得太多收益。

答案 1 :(得分:3)

在groovy脚本下面使用并使用闭包来解决OP问题。这将有助于决定用户为合并地图中每个键的值选择merge strategy

注意:脚本示例使用3个映射来确保脚本能够处理多个映射的合并。 即使要处理更多地图,此处提供的此解决方案也会扩展

在合并时,每个地图可能没有所有键,因此当用户尝试获取值时,可能会有null。因此,从传递给null的列表中删除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
上述代码的

输出

[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:如果您想要合并时的默认行为,请按合并顺序保留上一个映射的值,可以使用下面的闭包调用

newmap = getMergedMap(mapList) { list -> (list-null).last()   }
println newmap

结果:

[a:4, b:3, c:2, d:3, e:9]

您可以从此处快速测试脚本 Demo

<强> UPDATE2: 上述getMeredMap简单易读。当然,可以使用多个inject进行groovified /浓缩,如下图所示:

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 }
}

更新3
您还可以通过为合并值策略单独定义闭包来简化调用代码。 imo,这简化了一点。在内部合并时也处理空值而不是让用户在外面处理,这对于那些使用getMergedMap方法的人来说会更加干净。

//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) 

答案 2 :(得分:2)

这样就够了吗?

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]

答案 3 :(得分:0)

第一个可以通过以下方式完成:

/* 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
}

现在让我们在Groovy控制台上使用它:

mergeMaps([a: 10, b:2, c:3], [b:3, c:2, d:5])
Result: [a:10, b:5, c:5, d:5]

扩展的问题(更通用)可以通过一个小的调整来完成:

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
}

现在让我们在Groovy控制台上使用它:

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]

或者如果我们想要与乘法合并:

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]

答案 4 :(得分:0)

这是一个简单的解决方案,它收集唯一键,将每个键的值作为数组收集,并将lambda应用于每个键的值数组。在第一个中,lambda接受一个数组:

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]

在第二版中,lambda表达式采用两个值:

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]