按唯一值分组,同时求和/添加其他值

时间:2019-04-12 18:59:17

标签: ruby-on-rails ruby hash group-by sum

我有一个看起来像这样的数据结构:

arr = [
  {
    price: 2.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 200
   },
   {
    price: 2.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 200
   },
   {
    price: 14.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 500
   },
   {
    price: 14.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 500
  }
]

我需要将所有这些都按riff_code分组,同时将与该关税代码相对应的价格和金额相加。所以我的预期输出应该是:

[
  {
    price: 4.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 400
   },
   {
    price: 2.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 1000
   }
]

receipt_data[:order_items].group_by { |oi| oi[:tariff_code] }.values

上面使用的group_by语句将使我可以按riff_code分组,但是我无法找到一种将其他值求和的方法。我敢肯定,有一种光滑的单线方式可以做到这一点...

4 个答案:

答案 0 :(得分:2)

有两种解决此类问题的标准方法。我采用的一种方法是使用Hash#update(也称为merge!)的形式,该形式采用一个块来确定要合并的两个哈希中存在的键的值。另一种方法是使用Enumerable#group_by,我希望有人很快会用另一个答案。我不认为这两种方法在效率或可读性方面都是可取的。

arr.each_with_object({}) do |g,h|
  h.update(g[:tariff_code]=>g) do |_,o,n|
    { price: o[:price]+n[:price], unit: o[:unit], amount: o[:amount]+n[:amount] }
  end
end.values
  #=> [{:price=>4.0,  :unit=>"meter", :amount=>400},
  #    {:price=>28.0, :unit=>"yards", :amount=>1000}] 

请注意,values的接收者为:

{"4901.99"=>{:price=>4.0,  :unit=>"meter", :amount=>400},
{"6006.24"=>{:price=>28.0, :unit=>"yards", :amount=>1000}} 

答案 1 :(得分:2)

更详细:

grouped_items = arr.group_by { |oi| oi[:tariff_code] }
result = grouped_items.map do |tariff_code, code_items|
  price, amount = code_items.reduce([0, 0]) do |(price, amount), ci|
    [price + ci[:price], amount + ci[:amount]]
  end
  {
    price:       price,
    unit:        code_items.first[:unit],
    tariff_code: tariff_code,
    amount:      amount
  }
end
#[
#  {:price=>4.0, :unit=>"meter", :tariff_code=>"4901.99", :amount=>400}
#  {:price=>28.0, :unit=>"yards", :tariff_code=>"6006.24", :amount=>1000}
#]

答案 2 :(得分:2)

只是为了增加乐趣,答案就像@cary所说的那样使用group_by,并且大部分是复制Pavel的答案。这是非常糟糕的性能,并且仅在数组较小的情况下使用。它还使用sum,仅在Rails中可用。 (可以用纯红宝石中的.map { |item| item[:price] }.reduce(:+)代替)

arr.group_by { |a| a[:tariff_code] }.map do |tariff_code, items|
  {
    price: items.sum { |item| item[:price] },
    unit: items.first[:unit],
    tariff_code: tariff_code,
    amount: items.sum { |item| item[:amount] }
  }
end

如果它是一个带有方法而不是哈希的对象数组(可能是ActiveRecord对象),则可能会更小。

arr.group_by(&:tariff_code).map do |tariff_code, items|
  {
    price: items.sum(&:price]),
    unit: items.first[:unit],
    tariff_code: tariff_code,
    amount: items.sum(&:amount)
  }
end

答案 3 :(得分:1)

一种简单的方法,但是很容易添加用于求和和更改组密钥的新密钥。不确定效率,但这里MDIconButton的基准的500_000倍看起来不错

arr.map
#<Benchmark::Tms:0x00007fad0911b418 @label="", @real=1.480799000000843, @cstime=0.0, @cutime=0.0, @stime=0.0017340000000000133, @utime=1.4783359999999999, @total=1.48007>