我有一个看起来像这样的数据结构:
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
}
]
我需要按tariff_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
语句将允许我按tariff_code分组,但我无法找到一种对其他值求和的方法。 我敢肯定有一种巧妙的单行方法可以实现这一目标......
更详细:
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}
#]
只是为了增加乐趣,答案使用 group_by
正如@cary所说,并且主要复制 Pavel 的答案。这在性能上非常糟糕,并且仅在数组很小时才使用。此外,它还使用仅在 Rails 中可用的sum
。(可以用纯红宝石中的.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
有两种标准方法可以解决此类问题。我采用的一种方法是使用 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}}
一种简单的方法,但很容易添加新键进行求和和更改组键。不确定效率,但 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>
summ_keys = %i[price amount]
grouping_key = :tariff_code
result = Hash.new { |h, k| h[k] = {} }
arr.map do |h|
cumulative = result[h[grouping_key]]
h.each do |k, v|
case k
when *summ_keys
cumulative[k] = (cumulative[k] || 0) + h[k]
else
cumulative[k] = v
end
end
end
p result.values
# [{:price=>4.0, :unit=>"meter", :tariff_code=>"4901.99", :amount=>400},
# {:price=>28.0, :unit=>"yards", :tariff_code=>"6006.24", :amount=>1000}]