简体   繁体   中英

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}]

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM