Best way to aggregate a list of items and collect

2019-07-08 08:11发布

I'm wondering if there's a good fold (or map, reduce etc.) style solution to this problem.

Give a collection of purchases (order_items) I want to gather the totals for each product/sku.

Example collection:

[{sku: "A", price:10}, 
 {sku: "B", price:5}, 
 {sku: "C", price:2}, 
 {sku: "B", price:5}, 
 {sku: "A", price:10}, 
 {sku: "B", price:5}]

And get a result of:

{"A":20, "B":15, "C":2} 

At present I do it like so:

aggregate = order_items.each_with_object({}){|i,o|
  o[i[:sku]] ||= 0
  o[i[:sku]] += i[:price]
}

Which gives me what I want, but I want to know if there's a more elegant way to do this?

Obviously, if I pre-filter to a single SKU I can do a classic reduce ie.

# assuming sku is a flat array of values for a single sku type...
aggregate_sku = sku.reduce(:+)

However, I don't want to have to pre-filter the original collection. Is there a way to achieve this or am I already doing everything possible?

Any help is appreciated.

Edited to add clarity to the question. If you feel the need to vote to close, please post a comment first, so I can clarify.

Subtext: I'm not sure if map, reduce etc, has features (or more likely techniques) I don't yet understand, Thank you.

3条回答
混吃等死
2楼-- · 2019-07-08 08:15

Using Enumerable#group_by

agg = order_items.group_by { |item| item.sku }
agg.each { |sku, items| agg[sku] = items.map(&:price).reduce(:+) }
查看更多
Anthone
3楼-- · 2019-07-08 08:25
order_items.inject(Hash.new) {|hash, item| hash[item[:sku]] = (hash[item[:sku]] || 0) + item[:price]; hash}

It uses inject instead of each_with_object and simplifies the nil case in the aggregation. But it's more or less the same as in your question.


Please have a look at @7stud's answer, he does handles the 0 default value in a better way.

查看更多
Rolldiameter
4楼-- · 2019-07-08 08:35
order_items = [
 {sku: "A", price:10}, 
 {sku: "B", price:5}, 
 {sku: "C", price:2}, 
 {sku: "B", price:5}, 
 {sku: "A", price:10}, 
 {sku: "B", price:5}
]

aggregate = order_items.each_with_object(Hash.new(0)) do |item, acc|
  acc[ item[:sku] ] += item[:price]
end

--output:--
{"A"=>20, "B"=>15, "C"=>2}
查看更多
登录 后发表回答