Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

People also ask

How do you create a Hash in Ruby?

In Ruby you can create a Hash by assigning a key to a value with => , separate these key/value pairs with commas, and enclose the whole thing with curly braces.

What is the difference between a Hash and an array in Ruby?

Ruby's arrays and hashes are indexed collections. Both store collections of objects, accessible using a key. With arrays, the key is an integer, whereas hashes support any object as a key.

How Ruby Hash is implemented?

Well, Ruby's hash implementation will store the items in the same bin in the form of a linked list. If the identifier (the key) matches the one stored (it checks this with equal? ), it returns the value, and if it doesn't, it simply continues traversing the list.


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.


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}

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.


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}

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 }