问题
I saw this piece of code in this post, because I'm trying to sum values in an array of hashes based on some criteria.
Rails sum values in an array of hashes
array = [
{loading: 10, avg: 15, total: 25 },
{loading: 20, avg: 20, total: 40 },
{loading: 30, avg: 25, total: 55 }
]
sum = Hash.new(0)
array.each_with_object(sum) do |hash, sum|
hash.each { |key, value| sum[key] += value }
end
# => {:loading=>60, :avg=>60, :total=>120}
What I'm trying to do and I don't know how, is to sum total key if loading and avg appear with the same values more than once in this array. For instance.
array = [
{loading: 10, avg: 15, total: 25 },
{loading: 20, avg: 20, total: 40 }, # See here
{loading: 30, avg: 25, total: 55 },
{loading: 20, avg: 20, total: 80 }, # See here
{loading: 10, avg: 20, total: 46 }
]
The result would be:
[
{loading: 10, avg: 15, total: 25 },
{loading: 20, avg: 20, total: 120 }, # Results in this
{loading: 30, avg: 25, total: 55 },
{loading: 10, avg: 20, total: 46 }
]
I tried to modify this line
hash.each { |key, value| sum[key] += value }
Adding a conditional that checks if the value is repeated but I didn't succeed.
Any help, ideas or anything will be welcome.
回答1:
This seems work
array.group_by { |item| [item[:loading], item[:avg]] }.values.flat_map { |items| items.first.merge(total: items.sum { |h| h[:total] }) }
=> [{:loading=>10, :avg=>15, :total=>25}, {:loading=>20, :avg=>20, :total=>120}, {:loading=>30, :avg=>25, :total=>55}, {:loading=>10, :avg=>20, :total=>46}]
回答2:
You can use Enumerable#group_by and Hash#merge! to handle this as
array.group_by {|h| [h[:loading],h[:avg]]}.values.map do |a|
a.inject do |memo,h|
memo.merge!(h) do |_k,old_value,new_value|
old_value + new_value
end
end
end
#=> [{:loading=>10, :avg=>15, :total=>25},
# {:loading=>40, :avg=>40, :total=>120},
# {:loading=>30, :avg=>25, :total=>55},
# {:loading=>10, :avg=>20, :total=>46}]
Quick Breakdown
array.group_bygroup the elements by the loading and avg values this will return a hash of {grouped_values => [objects]}values[objects] from the above groupings (because we don't specifically care about the keys)mapinto a new Arrayinjectwill pass the first element as memo and will yield the second element on the first iteration. After that memo will be the return value from inject and the next element will be yielded to the block until there are no elements left.merge!with block syntax allows you to handle duplicate keys. In this case you want to add the values.
回答3:
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged.1 See the doc for the definitions of the block variables _k, o an n. The first variable (_k) begins with an underscore (often written simply _) to signify that that block variable is not used in the block calculation. (Note that @engineersmnky dd the same.)
arr = [
{loading: 10, avg: 15, total: 25 },
{loading: 20, avg: 20, total: 40 },
{loading: 30, avg: 25, total: 55 },
{loading: 20, avg: 20, total: 80 },
{loading: 10, avg: 20, total: 46 }
]
arr.each_with_object({}) { |g,h| h.update(g.values_at(:loading, :avg)=>g) { |_k,o,n|
o.merge(total: o[:total]+n[:total]) } }.values
#=> [{:loading=>10, :avg=>15, :total=>25},
# {:loading=>20, :avg=>20, :total=>120},
# {:loading=>30, :avg=>25, :total=>55},
# {:loading=>10, :avg=>20, :total=>46}]
Before .values is applied at the end, we will have constructed the following hash:
arr.each_with_object({}) { |g,h| h.update(g.values_at(:loading, :avg)=>g) { |_k,o,n|
o.merge(total: o[:total]+n[:total]) } }
#=> {[10, 15]=>{:loading=>10, :avg=>15, :total=>25},
# [20, 20]=>{:loading=>20, :avg=>20, :total=>120},
# [30, 25]=>{:loading=>30, :avg=>25, :total=>55},
# [10, 20]=>{:loading=>10, :avg=>20, :total=>46}}
Had @Ursus (whose answer preceded mine) taken this approach I would have used group_by. The two approaches seems to always be interchangeable. I don't think one is better; it's just a matter of personal preference. It seems that @engineersmnky couldn't make up his mind, so he used a bit of both.
1. I feel like I've written this sentence (verbatim) hundreds of times. ¯\_(ツ)_/¯
来源:https://stackoverflow.com/questions/43876712/sum-values-in-array-of-hash-if-they-have-the-same-value