【问题标题】:How to merge two maps in groovy如何在groovy中合并两个地图
【发布时间】:2017-03-09 03:12:02
【问题描述】:

问题:
如何在汇总映射之间的公共键值的同时合并映射。

输入:

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

输出

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

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

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

【问题讨论】:

标签: groovy


【解决方案1】:

这是一个简单的解决方案,它收集唯一键、每个键的值作为一个数组,并将 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]

【讨论】:

  • 感谢您的分享!这是我见过的最优雅的方式!
【解决方案2】:

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

注意:脚本示例使用 3 个地图以确保脚本能够处理多个地图的合并。 即使有更多地图需要处理,此处提供的此解决方案也会缩放

在合并时,可能每个映射可能没有所有键,因此当用户尝试获取值时,可能有null。因此从传递给Collection的列表中删除null

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

更新:如果你想要在合并时的默认行为,按照合并的顺序保留最后一张地图的值,可以使用下面的闭包调用

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

结果:

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

您可以从这里快速测试脚本Demo

更新 2: 以上getMeredMap 简单易读。当然,可以使用多个inject's groovified/condensed 到如下图在线:

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
您还可以通过为合并值策略单独定义闭包来简化调用代码。海事组织,这几乎没有简化。还在内部合并时处理空值,而不是让用户在外部处理,这对使用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) 

【讨论】:

    【解决方案3】:

    你可以使用注入?

    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 已经允许这样做了,你不会获得太多。

    【讨论】:

    • 谢谢。您能否也提供您对扩展问题的答案(更通用的版本,不仅使用 sum)。
    • 我使用您的方法尝试了问题的第二个(通用)部分: Map mergeMapsWith2(Map 1, Map 2, Closure cls) { (map1.keySet() + map2.keySet( )) .collectEntries { [(it): map1[it] && map2[it] ? cls.call(map1[it],map2[it]) : (map1[it] ?: map2[it])] } } 看起来有点复杂..这也是你的答案吗?抱歉,格式化是在 cmets 中拍摄的。
    • @Don:您还需要为操作传递身份。也许传入一个方法指针?我必须回到这个。
    【解决方案4】:

    这就够了吗?

    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]
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 2020-05-27
        • 1970-01-01
        • 2012-02-06
        • 2011-02-05
        • 1970-01-01
        相关资源
        最近更新 更多