Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic Ruby: data structure transformation

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).

like image 883
Kevin Weil Avatar asked Feb 26 '26 19:02

Kevin Weil


2 Answers

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
like image 119
Tor Erik Linnerud Avatar answered Mar 01 '26 17:03

Tor Erik Linnerud


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)}}
like image 38
SztupY Avatar answered Mar 01 '26 17:03

SztupY



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!