Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize a hash in the inject method to prevent a value being nil

Tags:

ruby

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.

like image 406
Allen Liu Avatar asked Nov 25 '25 14:11

Allen Liu


1 Answers

Solutions

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]
like image 197
Eric Duminil Avatar answered Nov 28 '25 04:11

Eric Duminil



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!