locoboy locoboy - 6 months ago 11
Ruby Question

How to sum multiple elements of nested arrays on unique keys

I have the following defined table. The index for each element in each row corresponds to the same field.

[[123.0, 23,"id1",34, "abc"],
[234.1,43, "id2", 24,"jsk"],
[423.5,53, "id1",1,"xyz"],
[1.4, 5, "id2",0,"klm"]]


In the above example I need to group and sum an output that sums each of the summable elements on the index for the unique identifier in the 3rd column. The result should look like this:

[[546.5,76, "id1",35],
[235.5,48, "id2",24]]


What's the best way to do this?

Answer

This is essentially the same as the solution to your previous question.

data = [ [ 123.0, 23, "id1", 34, "abc" ], 
         [ 234.1, 43, "id2", 24, "jsk" ],
         [ 423.5, 53, "id1",  1, "xyz" ],
         [   1.4,  5, "id2",  0, "klm" ] ]

sums = Hash.new {|h,k| h[k] = [0, 0, 0] }

data.each_with_object(sums) do |(val0, val1, id, val2, _), sums|
  sums[id][0] += val0
  sums[id][1] += val1
  sums[id][2] += val2
end
# => { "id1" => [ 546.5, 76, 35 ],
#      "id2" => [ 235.5, 48, 24 ] }

The main difference is that instead of giving the Hash a default value of 0, we're giving it a default proc that initializes missing keys with [0, 0, 0]. (We can't just do Hash.new([0, 0, 0]) because then every value would be a reference to a single Array instance, rather than each value having its own Array.) Then, inside the block, we add each value (val0 et al) to the corresponding elements of sums[id].

If you wanted an Array of Arrays instead of a Hash with the id at index 2, then at the end, you would have to add something like this:

.map {|id, vals| vals.insert(2, id) }

However, a Hash with the ids as keys makes more sense as a data structure.