What is the best way to convert an array to a hash in Ruby
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 }
Nathan Fritz
I work at the awesome Dave Ramsey company. More information at www.developwithpurpose.com!
Updated on July 08, 2022Comments
-
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 over 12 yearsOh, the eloquence! This is why I love Ruby
-
Stew over 12 yearsWARNING: answers using flatten will cause problems if you want Array keys or values.
-
Stew over 12 yearsI've posted an alternative solution below that will avoid problems with Array keys or values.
-
Cluster almost 12 yearsThis 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 almost 12 yearsIt'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 over 11 yearsHow! How! How!... How did you come up with this! Pure beauty I say.
-
brymck about 11 yearsFWIW, 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 recursiveflatten
that is destroying those, which is easy enough to avoid. -
Jeff McCune about 11 yearsInstead of using map, why not just specify the depth of flatten? For example:
h3 = Hash[*a3.flatten(1)]
instead ofh3 = Hash[*a3.flatten]
which would throw an error. -
superluminary over 10 yearsForgive the dimwit question. What does the * do?
-
Marc-André Lafortune over 10 yearsThis answer is not efficient and dangerous. It is also out of date. See my answer.
-
Marc-André Lafortune over 10 yearsThis answer is not efficient. It is also out of date. See my answer.
-
bluexuemei about 10 years
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ] h3 = Hash[*a3.flatten(1)]
-
Chris Bloom over 9 years+1 for the
a.inject({})
one-liner that allows for more flexible value assignments. -
coding addicted over 9 yearsThanks for the simplicity of the new .to_h method!
-
B Seven about 9 yearsI like the
to_h
method better than the above answers because it expresses the intent of converting after operating on the array. -
B Seven about 9 yearsYes, I think Marc-André's
to_h
is better. -
Iron Savior over 8 years@BSeven Neither
Array#to_h
norEnumerable#to_h
is in core ruby 1.9. -
Stew about 8 years@Marc-André Lafortune thank you, I have updated my answer to direct users to yours.
-
Dennis almost 8 years@superluminary the
*
explodes (aka unpacks) the array to an argument list of its elements. For exampleHash[1, 2]
returns the hash{ 1 => 2 }
. That's equivalent toa = [1, 2]; Hash[*a]
-
Camille Goudeseune over 7 yearsFor arrays larger than about 65,000, this fails with
stack level too deep (SystemStackError)
. (Ruby 1.9.3, Ubuntu 14.) -
nishant almost 7 yearsWhat 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 almost 7 years@NishantKumar that's a different question.
-
lindes over 6 yearsIt seems to me that the pertinent bits of this can be shortened to
Hash[*a1]
andHash[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 over 6 yearsThis 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 over 6 yearsIt's also possible to drop the
h = {}
from the second example via use of inject, ending up witha.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
-
Conor O'Brien about 6 yearsYou could do
a.each_slice(2).to_h
-
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 over 5 yearsCommenting here is not the way to go. Still, clearly one of you element looks like "a=b=c".