Search code examples
ruby-on-railsruby

Sum array values to hash


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.


Solution

  • You can solve this using Enumerable#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 }}