Reputation: 447
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
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
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
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
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
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