magicfishy
magicfishy

Reputation: 100

Ruby - Iterate over array of hashes and sum/compact the values of duplicate keys

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

Answers (2)

Chris
Chris

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

pjs
pjs

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

Related Questions