使用 groovy 合并 Map<String, List<String>> 结构的优雅方式

elegant way to merge Map<String, List<String>> structure by using groovy

我有一个嵌套映射结构:

Map<String, List<String>> case_pool = [
  dev : [
    funcA : ['devCaseA'] ,
    funcB : ['devCaseB'] ,
    funcC : ['devCaseC']
  ],
  'dev/funcA' : [
    funcA : ['performanceCaseA']
  ],
  'dev/funcA/feature' : [
    funcA : ['performanceCaseA', 'featureCase']
  ],
  staging : [
   funcB : ['stgCaseB'] ,
   funcC : ['stgCaseC']
  ]
]

想要得到结果,当branch.contains(case_pool.key),然后合并cases列表。 即:

String branch = 'dev/funcA/feature-1.0'

// will final get result of " 'dev' + 'dev/funcA' + 'dev/funcA/feature' ":
result: 
[
  funcA: [ "devCaseA", "performanceCaseA", "featureCase" ],
  funcB: [ "devCaseB" ],
  funcC: [ "devCaseC" ]
]

首先我使用了循环:

String branch = 'dev/funcA/feature-1.0'
def result = [:].withDefault { [] as Set }
case_pool.keySet().each {
  if ( branch.contains(it) ) {
    case_pool.get(it).each { k, v ->
      result[k].addAll(v)
    }
  }
}
println 'result: ' + result

其次,我正在使用闭包:


String branch = 'dev/funcA/feature-1.0'
def result = [:].withDefault { [] as Set }
case_pool.findAll{ k, v -> branch.contains(k) }.collectMany{ k, v -> v.collect{ c, l ->
    result[c].addAll(l)
}}
println 'result: ' + result

不过,我不喜欢.collectMany{ k, v -> v.collect{ c, l -> }}的方式。有没有更好的解决方案? (即:使用 groupBy 或其他)

顺便说一句,我已经尝试了 collectEntries,结果最终列表将替换所有:

String branch = 'dev/funcA/feature-1.0'
println case_pool.findAll{ k, v -> branch.contains(k) }.collect{ k, v -> v}.collectEntries{it}

result: [funcA:[performanceCaseA, featureCase], funcB:[devCaseB], funcC:[devCaseC]]

最后的funcA : ['performanceCaseA', 'featureCase']替换了全部funcA: []

正如 cfrick 所说,inject 在这里更好,即:

def result = case_pool.inject([:].withDefault { [] as Set }) { result, key, value ->
    if (branch.contains(key)) {
        value.each { k, v ->
            result[k] += v
        }
    }
    result
}

避免问题 java.io.NotSerializableException: groovy.lang.MapWithDefault in Jenkins, and even failed by using @NonCPS.

以下是情侣解决方案:

  1. 继续使用@tim_yates发布的inject([:].withDefault{}),只需将groovy.lang.MapWithDefault重新分配给java.util.LinkedHashMap
    Map<String, List<String>> result = [:]
    case_pool.inject([:].withDefault { [] as Set }) { res, key, value ->
      if (branch.contains(key)) {
        value.each { k, v -> res[k] += v }
      }; result
    }.collect { k, v -> result[k] = v }
    
  2. 使用[].flatten().unique()
    Map<String, List<String>> result = [:]
    case_pool.collect { key, value ->
      if (branch.contains(key)) {    
        value.collect { k, v -> 
          result[k] = [result.getOrDefault(k,[]), v].flatten().unique()
        }
      }
    }
    
  3. 使用 findAll{} + collect{}
    Map<String, List<String>> result = [:]
    case_pool.findAll{ k, v -> branch.contains(k) }.collect{ k, v -> 
      v.keySet().each {
        result[it] = (result.getOrDefault(it,[]) + v[it]).flatten().unique()
      }
    }