What's the "Rubyist" way to do the following data structure transformation:
I have
incoming = [ {:date => 20090501, :width => 2},
{:date => 20090501, :height => 7},
{:date => 20090501, :depth => 3},
{:date => 20090502, :width => 4},
{:date => 20090502, :height => 6},
{:date => 20090502, :depth => 2},
]
and I want to collapse these by :date, to end up with
outgoing = [ {:date => 20090501, :width => 2, :height => 7, :depth => 3},
{:date => 20090502, :width => 4, :height => 6, :depth => 2},
]
An array of arrays would also be fine at the last step, provided that the columns are in the same order in each row. Also, importantly, I do not know all the hash keys in advance (that is, I do not know :width, :height, or :depth -- they could be :cats, :dogs, and :hamsters).
If using Ruby 1.8.7 or Ruby 1.9+ the following code reads well:
incoming.group_by{|hash| hash[:date]}.map do |_, hashes|
hashes.reduce(:merge)
end
The underscore in the block attributes (_, hashes) indicates that we don't need/care about that particular attribute.
#reduce is an alias for #inject, which is used to reduce a collection into a single item. In the new Ruby versions it also accepts a symbol, which is the name of the method used to do the reduction.
It starts out by calling the method on the first item in the collection with the second item as the argument. It then calls the method again on the result with the third item as the argument and so on until there are no more items.
[1, 3, 2, 2].reduce(:+) => [4, 2, 2] => [6, 2] => 8
Here is a one liner :)
incoming.inject({}){ |o,i| o[i[:date]]||=[];o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}}
But actually the previous post is more clear, and might be faster too.
EDIT: with a bit of optimization:
p incoming.inject(Hash.new{|h,k| h[k]=[]}){ |o,i| o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With