What is the best way to convert an array to a hash in Ruby

145,119

Solution 1

NOTE: For a concise and efficient solution, please see Marc-André Lafortune's answer below.

This answer was originally offered as an alternative to approaches using flatten, which were the most highly upvoted at the time of writing. I should have clarified that I didn't intend to present this example as a best practice or an efficient approach. Original answer follows.


Warning! Solutions using flatten will not preserve Array keys or values!

Building on @John Topley's popular answer, let's try:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

This throws an error:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

The constructor was expecting an Array of even length (e.g. ['k1','v1,'k2','v2']). What's worse is that a different Array which flattened to an even length would just silently give us a Hash with incorrect values.

If you want to use Array keys or values, you can use map:

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

This preserves the Array key:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

Solution 2

Simply use Hash[*array_variable.flatten]

For example:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

Using Array#flatten(1) limits the recursion so Array keys and values work as expected.

Solution 3

The best way is to use Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

Note that to_h also accepts a block:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

Note: to_h accepts a block in Ruby 2.6.0+; for early rubies you can use my backports gem and require 'backports/2.6.0/enumerable/to_h'

to_h without a block was introduced in Ruby 2.1.0.

Before Ruby 2.1, one could use the less legible Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

Finally, be wary of any solutions using flatten, this could create problems with values that are arrays themselves.

Solution 4

Update

Ruby 2.1.0 is released today. And I comes with Array#to_h (release notes and ruby-doc), which solves the issue of converting an Array to a Hash.

Ruby docs example:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}

Solution 5

Edit: Saw the responses posted while I was writing, Hash[a.flatten] seems the way to go. Must have missed that bit in the documentation when I was thinking through the response. Thought the solutions that I've written can be used as alternatives if required.

The second form is simpler:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a = array, h = hash, r = return-value hash (the one we accumulate in), i = item in the array

The neatest way that I can think of doing the first form is something like this:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
Share:
145,119
Nathan Fritz
Author by

Nathan Fritz

I work at the awesome Dave Ramsey company. More information at www.developwithpurpose.com!

Updated on July 08, 2022

Comments

  • Nathan Fritz
    Nathan Fritz almost 2 years

    In Ruby, given an array in one of the following forms...

    [apple, 1, banana, 2]
    [[apple, 1], [banana, 2]]
    

    ...what is the best way to convert this into a hash in the form of...

    {apple => 1, banana => 2}
    
  • Igbanam
    Igbanam over 12 years
    Oh, the eloquence! This is why I love Ruby
  • Stew
    Stew over 12 years
    WARNING: answers using flatten will cause problems if you want Array keys or values.
  • Stew
    Stew over 12 years
    I've posted an alternative solution below that will avoid problems with Array keys or values.
  • Cluster
    Cluster almost 12 years
    This is the same as Hash[a3], since a3 == a3.map{|k,v| [k,v]} is true, it's actually the equivalent to a3.dup.
  • Cluster
    Cluster almost 12 years
    It's better to not try and do a catch-all solution for this. If your keys and values are paired as in [[key1,value1],[key2,value2]] then just pass it to Hash[] without fattening. Hash[a2] == Hash[*a2.flatten]. If the array is already flattened as in, [key1, value1, key2, value2] then just prefix the var with *, Hash[*a1]
  • Arindam
    Arindam over 11 years
    How! How! How!... How did you come up with this! Pure beauty I say.
  • brymck
    brymck about 11 years
    FWIW, if you really do want (more of a) one-size-fits-all version, you can also use Hash[*ary.flatten(1)], which will preserve array keys and values. It's the recursive flatten that is destroying those, which is easy enough to avoid.
  • Jeff McCune
    Jeff McCune about 11 years
    Instead of using map, why not just specify the depth of flatten? For example: h3 = Hash[*a3.flatten(1)] instead of h3 = Hash[*a3.flatten] which would throw an error.
  • superluminary
    superluminary over 10 years
    Forgive the dimwit question. What does the * do?
  • Marc-André Lafortune
    Marc-André Lafortune over 10 years
    This answer is not efficient and dangerous. It is also out of date. See my answer.
  • Marc-André Lafortune
    Marc-André Lafortune over 10 years
    This answer is not efficient. It is also out of date. See my answer.
  • bluexuemei
    bluexuemei about 10 years
    a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ] h3 = Hash[*a3.flatten(1)]
  • Chris Bloom
    Chris Bloom over 9 years
    +1 for the a.inject({}) one-liner that allows for more flexible value assignments.
  • coding addicted
    coding addicted over 9 years
    Thanks for the simplicity of the new .to_h method!
  • B Seven
    B Seven about 9 years
    I like the to_h method better than the above answers because it expresses the intent of converting after operating on the array.
  • B Seven
    B Seven about 9 years
    Yes, I think Marc-André's to_h is better.
  • Iron Savior
    Iron Savior over 8 years
    @BSeven Neither Array#to_h nor Enumerable#to_h is in core ruby 1.9.
  • Stew
    Stew about 8 years
    @Marc-André Lafortune thank you, I have updated my answer to direct users to yours.
  • Dennis
    Dennis almost 8 years
    @superluminary the * explodes (aka unpacks) the array to an argument list of its elements. For example Hash[1, 2] returns the hash { 1 => 2 }. That's equivalent to a = [1, 2]; Hash[*a]
  • Camille Goudeseune
    Camille Goudeseune over 7 years
    For arrays larger than about 65,000, this fails with stack level too deep (SystemStackError). (Ruby 1.9.3, Ubuntu 14.)
  • nishant
    nishant almost 7 years
    What if I have an array as [[apple, 1], [banana, 2], [apple, 3], [banana, 4]] and I want the output as {"apple" =>[1,3], "banana"=>[2,4]}?
  • Marc-André Lafortune
    Marc-André Lafortune almost 7 years
    @NishantKumar that's a different question.
  • lindes
    lindes over 6 years
    It seems to me that the pertinent bits of this can be shortened to Hash[*a1] and Hash[a2] without no change on this particular set of data, and without losing things one might lose with different data; see an answer I recently added for more details. Even .flatten(1) has the potential to lose important stuff here.
  • lindes
    lindes over 6 years
    This also works without the splat (*) and flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))] => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. More detail in an answer I added.
  • lindes
    lindes over 6 years
    It's also possible to drop the h = {} from the second example via use of inject, ending up with a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
  • Conor O'Brien
    Conor O'Brien about 6 years
    You could do a.each_slice(2).to_h
  • Vinit Sharma
    Vinit Sharma over 5 years
    @Marc-AndréLafortune I have used something like this properties.map{|element| element.split '=' }.to_h and it gives me some error like this: wrong array length at 20 (expected 2, was 3) My created array is something like this: [key1 = value1, key2 = value2, ...] Can anyone suggest as to what thing is incorrect with my code. Thanks.
  • Marc-André Lafortune
    Marc-André Lafortune over 5 years
    Commenting here is not the way to go. Still, clearly one of you element looks like "a=b=c".