cymorg
cymorg

Reputation: 447

Handling array of hashes

Given an array of Ruby hashes like this:

[{"lib1"=>"30"}, {"lib2"=>"30"}, {"lib9"=>"31"}, {"lib2"=>"31"}, {"lib3"=>"31"}, {"lib1"=>"32"}, {"lib2"=>"32"}, {"lib1"=>"33"}, {"lib3"=>"36"}, {"lib2"=>"36"}, {"lib1"=>"37"}]

How do I get a hash like this:

{"lib1"=>[30,32,33,37], lib2=>[30,31,32,36], lib3=>[31,36], lib9=>[31]}

Upvotes: 1

Views: 130

Answers (5)

Daniël Knippers
Daniël Knippers

Reputation: 3055

Just to add to the spectrum; you can avoid each_with_object by defining the resulting Hash up front and use a regular .each loop. I find it easier to read than the former approach.

grouped = {}
hashes.each do |hash|
  hash.each do |key, value|
    (grouped[key] ||= []) << value.to_i
  end
end

Upvotes: 0

dbenhur
dbenhur

Reputation: 20408

Alternate to Andrew's, avoids flatten and to_a, just nested iteration. Will gather multiple keys from element hashes in source array if present.

a.each_with_object({}) do |element,result|
  element.each do |k,v|
    (result[k] ||= []) << v.to_i
  end
end

Golfed to one-line:

a.each_with_object({}) {|e,r| e.each {|k,v| (r[k] ||= []) << v.to_i } }

I would note that this version examines each source element only once, while the to_a/flatten and group_by answers involve multiple iterations over the source or transformations of the source.

Andrew makes a good point that constant factors in big-O algorithm complexity are often a wash in reality. I put together a quick benchmark of the answers supplied so far (correcting them all to cast values to fixnum as the OP example implies). My nested iteration approach does turn out to be somewhat (23-45%) faster with the OPs example source data:

ruby 1.9.2p318 (2012-02-14 revision 34678) [x86_64-linux]
Rehearsal ------------------------------------------------------------
to_a_flat                  3.100000   0.000000   3.100000 (  3.105873)
to_a_flat_construct        4.060000   0.000000   4.060000 (  4.076938)
group_by_each              3.010000   0.000000   3.010000 (  3.015367)
group_by_each_construct    3.040000   0.000000   3.040000 (  3.050500)
nested_iter                2.300000   0.000000   2.300000 (  2.307776)
-------------------------------------------------- total: 15.510000sec

                               user     system      total        real
to_a_flat                  3.080000   0.000000   3.080000 (  3.096301)
to_a_flat_construct        4.050000   0.000000   4.050000 (  4.059409)
group_by_each              2.980000   0.000000   2.980000 (  2.997074)
group_by_each_construct    3.050000   0.000000   3.050000 (  3.057770)
nested_iter                2.300000   0.000000   2.300000 (  2.311855)

Upvotes: 2

Andrew Marshall
Andrew Marshall

Reputation: 96944

a = [{"lib1"=>"30"}, {"lib2"=>"30"}, {"lib9"=>"31"}, {"lib2"=>"31"}, {"lib3"=>"31"}, {"lib1"=>"32"}, {"lib2"=>"32"}, {"lib1"=>"33"}, {"lib3"=>"36"}, {"lib2"=>"36"}, {"lib1"=>"37"}]

a.map(&:to_a).flatten(1).each_with_object({}) do |(k, v), h|
  h[k] ||= []
  h[k] << v
end
#=> {"lib1"=>["30", "32", "33", "37"],
#    "lib2"=>["30", "31", "32", "36"],
#    "lib9"=>["31"],
#    "lib3"=>["31", "36"]}

Alternatively:

Hash[a.map(&:to_a).flatten(1).group_by(&:first).map { |k, v| [k, v.map(&:last)] }]

If you're willing to use Facets then this becomes absurdly simple with collate:

a.inject(:collate)

Upvotes: 3

sawa
sawa

Reputation: 168101

Similar to DigitalRoss's, but I would do it in place:

array.group_by{|h| h.keys.first}.each{|_, a| a.map!{|h| h.values.first}}

Upvotes: -1

DigitalRoss
DigitalRoss

Reputation: 146073

t = [{"lib1"=>"30"}, {"lib2"=>"30"}, {"lib9"=>"31"}, {"lib2"=>"31"},
  {"lib3"=>"31"}, {"lib1"=>"32"}, {"lib2"=>"32"}, {"lib1"=>"33"},
  {"lib3"=>"36"}, {"lib2"=>"36"}, {"lib1"=>"37"}]
result = {}

t.group_by { |x| x.keys.first }.each_pair do |k, v|
  result[k] = v.map { |e| e.values.first }
end

Or, for a more purely functional version and fitting, sort-of, on the all-important one-line (it is Ruby, after all) ...

Hash[t.group_by { |x| x.keys[0] }.map { |k, v| [k, v.map { |e| e.values[0] }]}]

Upvotes: 2

Related Questions