Reputation: 100
arr = [{"94838"=>30.0}, {"94916"=>2.0}, {"94916"=>10.0}]
I'm trying to iterate over each of the hashes and sum the values of all matching keys. Furthermore, to merge each hash into one (since there will be no more chance of dupe keys)
The expected output would be:
{"94838"=>30.0, "94916"=>12.0}
I found a way to do this but it seems gross -
arr.inject do |id, qty|
id.merge(qty) {|_k, old_v, new_v| old_v + new_v }
end
.compact
.sort
.to_h
I guess my question to more experienced programmers is; does this look OK? I just can't help but feel like there's a better way to do this - thank you :)
Upvotes: 0
Views: 1167
Reputation: 36611
Well, we can get all keys with:
uniq_keys = arr.map(&:keys).flatten.uniq
We can then map that to the values of those in the hashes and use #compact
to throw away the nils for hashes that don't have that key, use #sum
to sum up the numbers, then turn that to a hash.
uniq_keys.map { |k| [k, arr.map { |h| h[k] }.compact.sum] }.to_h
And we end up with:
{"94838"=>30.0, "94916"=>12.0}
This can also be solved using #each_with_object
.
arr.each_with_object({}) { |hsh, result|
hsh.each_pair { |k, v|
result[k] ||= 0
result[k] += v
}
}
# => {"94838"=>30.0, "94916"=>12.0}
Here we're iterating with a hash object called result
. For each hash in the array, we iterate over its key/value pairs, setting them by default to 0
in result
if they don't yet exist in result
, before adding their current value.
Upvotes: 1
Reputation: 19855
What's wrong with a straightforward iteration through all the items?
arr = [{"94838"=>30.0}, {"94916"=>2.0}, {"94916"=>10.0}]
hsh = Hash.new(0) # Default value for each key is zero
# For each hash in arr, iterate through each key/value pair and
# increment the destination total associated with the key by the
# current value. Can use increment because of the zero default.
arr.each { |h| h.each { |k, v| hsh[k] += v } }
p hsh # Produces {"94838"=>30.0, "94916"=>12.0} as desired
Upvotes: 3