如何在Ruby中对数组进行分组和求和?

sxpgvts3  于 2023-05-06  发布在  Ruby
关注(0)|答案(7)|浏览(130)

我有一个这样的数组数组:

ar = [[5, "2014-01-27"],
[20, "2014-01-28"],
[5, "2014-01-28"],
[10, "2014-01-28"],
[15, "2014-01-29"],
[5, "2014-01-29"],
[5, "2014-01-30"],
[10, "2014-01-30"],
[5, "2014-01-30"]]

我最终需要做的是按日期对数组项进行分组,并对每个子数组的第一项中的数字求和。
因此,输出将类似于:

[[5, "2014-01-27"],
[35, "2014-01-28"],
[20, "2014-01-29"],
[20, "2014-01-30"]]
1tuwyuhd

1tuwyuhd1#

ar.group_by(&:last).map{ |x, y| [y.inject(0){ |sum, i| sum + i.first }, x] }
编辑以添加说明:
我们按最后一个值(日期)分组,得到一个哈希值:

{"2014-01-27"=>[[5, "2014-01-27"]], "2014-01-28"=>[[20, "2014-01-28"], [5, "2014-01-28"], [10, "2014-01-28"]], "2014-01-29"=>[[15, "2014-01-29"], [5, "2014-01-29"]], "2014-01-30"=>[[5, "2014-01-30"], [10, "2014-01-30"], [5, "2014-01-30"]]}

然后用x作为哈希键,y作为[[number, date], [number, date]]对的数组来Map。
.inject(0)意味着sum0开始,然后我们将每个数组的第一项(数字)添加到该总和,直到所有数组都被迭代并且所有数字都被添加。
然后我们做[y, x],其中x是哈希键(日期),y是所有数字的和。
这种方法很有效,因为我们使用injection来避免Map数组两次,并且不必在之后反转值,因为我们在Map时交换了它们的位置。
编辑:有趣的是,@bjhaid和我的答案之间的基准很接近:

user     system      total        real
5.117000   0.000000   5.117000 (  5.110292)
5.632000   0.000000   5.632000 (  5.644323)

1000000迭代-我的方法最慢

oalqel3c

oalqel3c2#

h = ar.group_by(&:last)
h.keys.each{|k| h[k] = h[k].map(&:first).inject(:+)}
h.map(&:reverse)
r6l8ljro

r6l8ljro3#

result = {}
ar.map{|v,date| result[date] ||= 0; result[date] += v}

然后你得到一个哈希值,其中的键是日期,值是和,你真的需要结果是一个数组吗?看起来你需要一个散列,但我不知道上下文
也许你甚至不需要在ruby中这样做,如果这些都来自一个db,你可以对查询进行分组和求和

6mzjoqzu

6mzjoqzu4#

ar.each_with_object(Hash.new(0)) { |x,hash| hash[x[1]] += x[0] }.map(&:reverse)
=> [[5, "2014-01-27"], [35, "2014-01-28"], [20, "2014-01-29"], [20, "2014-01-30"]]

说明

第一部分使用Hash.new作为提供给Enumerable#each_with_object的对象来生成Hash,它将其键设置为日期(数组的第二个索引),将值设置为数组的第一个索引的和

[29] pry(main)> ar.each_with_object(Hash.new(0)) { |x,hash| hash[x[1]] += x[0] }
=> {"2014-01-27"=>5, "2014-01-28"=>35, "2014-01-29"=>20, "2014-01-30"=>20}

第二部分使用Enumerable#map,它将哈希中的每个keyvalue对视为一个数组,该数组将被生成到block/proc,在每个生成的对上调用Array#reverse以反转并生成最终数组

[30] pry(main)> {"2014-01-27"=>5, "2014-01-28"=>35, "2014-01-29"=>20, "2014-01-30"=>20}.map(&:reverse)
=> [[5, "2014-01-27"], [35, "2014-01-28"], [20, "2014-01-29"], [20, "2014-01-30"]]
ryhaxcpt

ryhaxcpt5#

我更喜欢@sawa的解决方案,它使用group_by,但这里有另一种方法,有助于说明这里可能的方法的多样性。
首先将数组转换为散列,以日期作为键

h = ar.each_with_object(Hash.new {|h,k| h[k] = []}) { |(x,d),h| h[d] << x }
  # => {"2014-01-27"=>[5],
  #     "2014-01-28"=>[20, 5, 10],
  #     "2014-01-29"=>[15, 5],

接下来,将此哈希中的每个值(数组)替换为其元素的总和:

h.keys.each { |k| h[k] = h[k].reduce(:+) }
  #   => ["2014-01-27", "2014-01-28", "2014-01-29", "2014-01-30"]
  # h => {"2014-01-27"=>5 , "2014-01-28"=>35,
  #       "2014-01-29"=>20, "2014-01-30"=>20}

请注意,这个表达式返回一个键数组,但散列h现在具有所需的值。因此,我们无法链接到最后的语句:

h.map(&:reverse).sort_by(&:first)
  # => [[ 5, "2014-01-27"], [35, "2014-01-28"],
  #     [20, "2014-01-29"], [20, "2014-01-30"]]

我这样写的原因之一是鼓励你考虑使用哈希g作为最终结果,而不是另一个数组。(这也是@sawa的解决方案中前两行之后的h的值)。考虑这是否在代码中的后续操作中更有意义。
大部分内容都很简单,但第一个each with object需要一些解释。object是一个散列,在块中由局部变量h表示。此哈希由以下人员创建:

Hash.new { |h,k| h[k] = [] }

这使得默认值为空数组。第一次通过块,d => "2014-01-27"。由于哈希最初为空,因此它没有密钥"2014-01-27"。因此,h["2014-01-27"]被分配了默认值[],之后是h["2014-01-27"] << 5,结果是h => {"2014-01-27" => 5}

wgxvkvu9

wgxvkvu96#

ar.group_by(&:last).map{ |d,g| [g.map(&:first).inject(:+), d] }
5m1hhzi4

5m1hhzi47#

我觉得下面这句话没那么深奥

ar.group_by(&:last).map {|k, v| [v.map {|e| e[0]}.sum, k]}

相关问题