I am trying to compile all the values for a given hash key in a list of hashes. I have the following which works.
[{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}].inject({}) do |hash, item|
item.each do |key, value|
hash[key] = [] if hash[key].nil?
hash[key] << value
end
hash
end
Here is the result which is great:
{:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
My question: is there a more elegant way to initializing the hash so I don't need to check for the nil case in the following line?
hash[key] = [] if hash[key].nil?
I have tried Hash.new(0) as the default for the inject method but it doesn't work. I'm sure there is a more elegant way to do this. Thanks.
You can use each_with_object :
array = [{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}]
empty_hash = Hash.new{ |h, k| h[k] = [] }
hash = array.each_with_object(empty_hash) do |small_hash, hash|
small_hash.each do |k, v|
hash[k] << v
end
end
p hash
#=> {:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
A shorter, but more unusual version is here :
hash = array.each_with_object(Hash.new{ [] }) do |small_hash, hash|
small_hash.each {|k, v| hash[k] <<= v }
end
p hash
#=> {:a=>[1, 4, 7], :b=>[2, 5, 8], :c=>[3, 6, 9]}
Both return {:a=>[1], :b=>[2]} for [{a: 1}, {b: 2}], as the OP specified.
hash[k] <<= v is a weird (and probably inefficient) trick. It is equivalent to :
hash[k] = (hash[k] << v)
The assignment is needed because the hash default hasn't been properly initialized, and a new array is being generated for every hash lookup, without being saved as a value :
h = Hash.new{ [] }
p h[:a] << 1
#=> [1]
p h[:a]
#=> []
p h[:a] <<= 1
#=> [1]
p h[:a]
#=> [1]
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