Echen
Echen

Reputation: 83

How to combine the value of multiple hashes within an array by the same key

I have an array of hashes like so:

[{"apple"=>5}, {"banana"=>4}, {"orange"=>6}, {"apple"=>4}, {"orange"=>2}]

How I do get to:

[{"apple"=>9}, {"banana"=>4}, {"orange"=>8}]

Upvotes: 1

Views: 244

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

There are many ways, as you will soon see. Here's one:

arr = [{"apple"=>5}, {"banana"=>4}, {"orange"=>6}, {"apple"=>4}, {"orange"=>2}]

arr.flat_map(&:to_a)
   .group_by(&:first)
   .map { |k,a| { k=>(a.reduce(0) { |tot,(_,v)| tot+v }) } } 
  #=> [{"apple"=>9}, {"banana"=>4}, {"orange"=>8}]

The steps:

a = arr.flat_map(&:to_a)
  #=> [["apple",5], ["banana",4], ["orange",6], ["apple",4], ["orange",2]] 
b = a.group_by(&:first)
  #=> {"apple"=>[["apple", 5], ["apple", 4]],
  #   "banana"=>[["banana", 4]],
  #   "orange"=>[["orange", 6], ["orange", 2]]} 
b.map { |k,a| { k=>(a.reduce(0) { |tot,(_,v)| tot+v }) } }
  #=> [{"apple"=>9}, {"banana"=>4}, {"orange"=>8}] 

Let's take a closer look at b.map:

enum = b.map
  #=> #<Enumerator: {
  #     "apple"=>[["apple", 5], ["apple", 4]],
  #     "banana"=>[["banana", 4]],
  #     "orange"=>[["orange", 6], ["orange", 2]]
  #   }:map> 

The first element of enum is passed (by Enumerator#each, which in turn calls Array#each) to the block and assigned to the block variables. We can simulate that using Enumerator#next:

 k,a = enum.next
   #=> ["apple", [["apple", 5], ["apple", 4]]]
 k #=> "apple"
 a #=> [["apple", 5], ["apple", 4]]

To calculate:

 c = a.reduce(0) { |tot,(_,v)| tot+v }
   #=> 9

the first element of a is passed to the block and the block variables are assigned:

 tot, (_,v) = 0, ["apple", 5]
     #=> [0, ["apple", 5]] 
 tot #=> 0
 v   #=> 5

We then compute:

 tot + 5
   #=> 0+5 => 5

which is returned to reduce to become the updated value of tot. The second value of a is passed in:

 tot, (_,v) = 5, ["apple", 4]
 tot #=> 5
 v   #=> 4

and we calculate and return:

 tot+4
   # 5+4 => 9

so:

 { k=>tot }
   #=> { "apple"=>9 }

is the mapped value of the first element of a. The remaining mapped values are computed similarly.

Upvotes: 1

mu is too short
mu is too short

Reputation: 434665

There's also:

cache = Hash.new { |h, k| h[k] = { k => 0 } }
aoh.flat_map(&:to_a)
   .each_with_object(cache) { |(k,v),h| h[k][k] += v }
   .values

Or in more pieces to be a little clearer:

cache   = Hash.new { |h, k| h[k] = { k => 0 } }
sum     = -> ((k, v), h) { h[k][k] += v }
summary = aoh.flat_map(&:to_a)
             .each_with_object(cache, &sum)
             .values

The somewhat odd looking cache Hash does two things at once:

  1. Keeps track of which keys have been seen so far in its keys.
  2. Keeps track of the final results we want in its values.

Upvotes: 2

Related Questions