Home > Mobile >  Sum array values to hash
Sum array values to hash

Time:09-16

I have an array like

[
    [358, 202102, 5],
    [358, 202112, 5],
    [358, 202102, 10],
    [311, 202103, 5],
    [311, 202101, 1],
    [311, 202101, 1],
    [311, 202115, 8],
    [311, 202101, 1],
    [311, 202101, 1]
]

I need a hash like this:

{
    358 => { 
        202102 => 15,
        202112 => 5
    },
    311 => {
        202103 => 5,
        202101 => 4,
        202115 => 8
    }
}

The order does not matter. Only the third values from each row of the array must be added up.

CodePudding user response:

You can solve this using Enumberable#each_with_object as follows

a = [
    [358, 202102, 5],
    [358, 202112, 5],
    [358, 202102, 10],
    [311, 202103, 5],
    [311, 202101, 1],
    [311, 202101, 1],
    [311, 202115, 8],
    [311, 202101, 1],
    [311, 202101, 1]
]

a.each_with_object(Hash.new {|h,k| h[k] = Hash.new(0)}) do |(k1,k2,val), obj| 
  obj[k1][k2]  = val 
end 
# => {358=>{202102=>15, 202112=>5}, 311=>{202103=>5, 202101=>4, 202115=>8}}

What this does is iterates over the Array (a) with an accumulator Object (obj).

This object is a Hash with a default proc where every time a new key is added its value is assigned as a new Hash with a default value of 0.

In the block arguments we deconstruct the Array into 3 arguments (k1,k2,val) so on first pass it would look something like this:

k1 = 358
k2 = 202102
val = 5 
obj[k1] 
#=> {358=> {}} 
obj[k1][k2] 
#=> {358 => {202102 => 0 }}
obj[k1][k2]  = val
obj
#=> {358 => {202102 => 5 }}

CodePudding user response:

Another way is to use the methods Hash#update (aka merge!) and Hash#merge that employ blocks to determine the values of keys that are present in both hashes being merged. See the docs for definitions of the three block variables. I've used an underscore for the first block variables (the common key of the hashes being merged) to signify that it is not used in the block calculation.

a = [
    [358, 202102, 5],
    [358, 202112, 5],
    [358, 202102, 10],
    [311, 202103, 5],
    [311, 202101, 1],
    [311, 202101, 1],
    [311, 202115, 8],
    [311, 202101, 1],
    [311, 202101, 1]
]
a.each_with_object({}) do |(x,y,n),h|
  h.update(x=>{y=>n}) do |_,old1,new1|
    old1.merge(new1) { |_,old2,new2| old2 new2 }
  end
end
  #=> {358=>{202102=>15, 202112=>5}, 311=>{202103=>5, 202101=>4, 202115=>8}}

CodePudding user response:

At first I create a hash of hashes with the default value 0. After that I just add the values of the hash of hashes up based on the last value in the array.

aoa =
[
    [358, 202102, 5],
    [358, 202112, 5],
    [358, 202102, 10],
    [311, 202103, 5],
    [311, 202101, 1],
    [311, 202101, 1],
    [311, 202115, 8],
    [311, 202101, 1],
    [311, 202101, 1]
]


hash = Hash.new { |h,k| h[k] = Hash.new(0) }
aoa.each { |ary|  hash[ary[0]][ary[1]]  = ary[2]}

pp hash

output:

{358=>{202102=>15, 202112=>5}, 311=>{202103=>5, 202101=>4, 202115=>8}}
  • Related