【问题标题】:Group and sum collection in GroovyGroovy 中的分组和求和收集
【发布时间】:2012-11-08 19:29:42
【问题描述】:

我有一组对象,我想按月份、名称和总和进行分组:

def things = [
    [id:1, name:"fred", total:10, date: "2012-01-01"],
    [id:2, name:"fred", total:10, date: "2012-01-03"],
    [id:3, name:"jane", total:10, date: "2012-01-04"],
    [id:4, name:"fred", total:10, date: "2012-02-11"],
    [id:5, name:"jane", total:10, date: "2012-01-01"],
    [id:6, name:"ted", total:10, date: "2012-03-21"],
    [id:7, name:"ted", total:10, date: "2012-02-09"]
];

我希望输出是:

[
 "fred":[[total:20, month:"January"],[total:10, month:"February"]],
 "jane":[[total:20,month:"January"]],
 "ted" :[[total:10, month:"February"],[total:10, month:"March"]]
]

或类似的东西。使用 groovy/grails 完成此任务的最佳方法是什么?

【问题讨论】:

    标签: grails groovy


    【解决方案1】:

    以下几行

    things.inject([:].withDefault { [:].withDefault { 0 } } ) { 
        map, v -> map[v.name][Date.parse('yyyy-MM-dd', v.date).format('MMMM')] += v.total; map 
    }
    

    会给你这个结果:

    [fred:[January:20, February:10], jane:[January:20], ted:[March:10, February:10]]
    

    (适用于 Groovy >= 1.8.7 和 2.0)

    【讨论】:

      【解决方案2】:

      我最终得到了

      things.collect { 
        // get the map down to name, total and month
        it.subMap( ['name', 'total' ] ) << [ month: Date.parse( 'yyyy-MM-dd', it.date ).format( 'MMMM' ) ]
        // Then group by name first and month second
      }.groupBy( { it.name }, { it.month } ).collectEntries { k, v ->
        // Then for the names, collect
        [ (k):v.collectEntries { k2, v2 ->
          // For each month, the sum of the totals
          [ (k2): v2.total.sum() ]
        } ]
      }
      

      为了得到与安德烈相同的结果,更短,更好的答案;-)

      编辑

      有点短,但还是没那么好......

      things.groupBy( { it.name }, { Date.parse( 'yyyy-MM-dd', it.date ).format( 'MMMM' ) } ).collectEntries { k, v ->
        [ (k):v.collectEntries { k2, v2 ->
          [ (k2): v2.total.sum() ]
        } ]
      }
      

      【讨论】:

      • @AndreSteingress 我无法抗拒关于修改列表和地图的问题;-)
      【解决方案3】:

      这是一个与其他解决方案执行相同操作的解决方案,但使用 GPar 并行执行。可能有更严格的解决方案,但这个解决方案确实适用于测试输入。

      @Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')
      
      import static groovyx.gpars.GParsPool.*
      
      //def things = [...]
      
      withPool {
          def mapInner = { entrylist ->
               withPool{
                   entrylist.getParallel()
                       .map{[Date.parse('yyyy-MM-dd', it.date).format('MMMM'), it.total]}
                       .combine(0) {acc, v -> acc + v}
               }
          }
      
          //for dealing with bug when only 1 list item
          def collectSingle = { entrylist ->
              def first = entrylist[0]
              return [(Date.parse('yyyy-MM-dd', first.date).format('MMMM')) : first.total]
          }
      
          def result = things.parallel
              .groupBy{it.name}.getParallel()
              .map{ [(it.key) : (it.value?.size())>1?mapInner.call(it.value):collectSingle.call(it.value) ] }
              .reduce([:]) {a, b -> a + b}
      
      
          println "result = $result"
      }
      

      【讨论】:

      • 我不确定这是使用 GPar 的最紧凑/最好/最快的方式。如果有人有替代的并行解决方案,请也发布或对此发表评论。
      猜你喜欢
      • 1970-01-01
      • 2019-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多