215

Convert this Array:

a = ["item 1", "item 2", "item 3", "item 4"] 

...to a Hash:

{ "item 1" => "item 2", "item 3" => "item 4" }

i.e. elements at even indexes are keys and odd ones are values.

1
  • Is there a comparisson of the performance of the different approaches? I commented a list of some ideas at this related question Commented Aug 8, 2023 at 14:58

9 Answers 9

377
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

That's it. The * is called the splat operator.

One caveat per @Mike Lewis (in the comments): "Be very careful with this. Ruby expands splats on the stack. If you do this with a large dataset, expect to blow out your stack."

So, for most general use cases this method is great, but use a different method if you want to do the conversion on lots of data. For example, @Łukasz Niemier (also in the comments) offers this method for large data sets:

h = Hash[a.each_slice(2).to_a]
Sign up to request clarification or add additional context in comments.

14 Comments

@tester, the * is called the splat operator. It takes an array and converts it a literal list of items. So *[1,2,3,4] => 1, 2, 3, 4. In this example, the above is equivalent to doing Hash["item 1", "item 2", "item 3", "item 4"]. And Hash has a [] method that accepts a list of arguments (making even indexes keys and odd indexes values), but Hash[] does not accept an array, so we splat the array using *.
Be very careful with this. Ruby expands splats on the stack. If you do this with a large dataset, expect to blow out your stack.
On big data tables you can use Hash[a.each_slice(2).to_a].
@Kevin, the stack uses a small area of memory that the program allocates and reserves for certain specific operations. Most commonly, it is used to keep a stack of the methods that have been called so far. This is the origin of the term stack trace, and this is also why an infinitely recursive method can cause a stack overflow. The method in this answer also uses the stack, but since the stack is only a small area of memory, if you try this method with a large array, it will fill up the stack and cause an error (an error along the same lines as a stack overflow).
Now it is possible to do a.each_slice(2).to_h (at least in YARV).
|
137

Ruby 2.1.0 introduced a to_h method on Array that does what you require if your original array consists of arrays of key-value pairs: http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-to_h.

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

2 Comments

for pre 2.1.0 ruby versions, you can use the Hash::[] method to get the similar results, as long as you have pairs of a nested array. so a =[[:foo, :1], [bar, 2]] --- Hash[a] => {:foo=>1, :bar=>2}
@AfDev, indeed, thanks. You're correct (when ignoring the minor typos: bar needs to be a symbol, and the symbol :2 should be an integer. So, your expression corrected is a = [[:foo, 1], [:bar, 2]]).
32

Just use Hash.[] with the values in the array. For example:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

2 Comments

@Marius: *arr converts arr into an argument list, so this is calling the [] method of Hash with the contents of arr as arguments.
Consider the remarks on the splat operator as described in this answer.
26

Or if you have an array of [key, value] arrays, you can do:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

5 Comments

Nope. It would return { [1, 2] => [3, 4] }. And since the question's title says "Array to Hash" and the built-in "Hash to Array" method does: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]], I thought more than one could end here trying to get the inverse of the built-in "Hash to Array" method. Actually, that's how I ended here anyway.
Sorry, I added a spare asterisk. Hash[arr] will do the job for you.
IMHO better solution: Hash[*array.flatten(1)]
Yossi: Sorry for raising the dead, but there is one bigger problem with his answer, and that is the use of #inject method. With #merge!, #each_with_object should have been used. If #inject is insisted upon, #merge rather than #merge! should have been used.
Now days you can do this short and sweet: [[1, 2], [3, 4]].to_h
17

This is what I was looking for when googling this:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

6 Comments

You don't want to use merge, it constructs and discards a new hash per loop iteration and is very slow. If you've got a array of hashes try [{a:1},{b:2}].reduce({}, :merge!) instead - it merges everything into the same (new) hash.
You can also do .reduce(&:merge!)
[{a: 1}, {b: 2}].reduce(&:merge!) evaluates to {:a=>1, :b=>2}
This works because inject/reduce has a feature where you can omit the argument, in which case it uses the first argument of the array to operate on as the input argument, and the rest of the array as the array. Combine that with symbol-to-proc and you get this concise construction. In other words [{a: 1}, {b: 2}].reduce(&:merge!) is the same as [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) } which is the same as [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
two caveats: (1) this mutates the original first element of the array in place. if you don't want to do that, you either have to explicitly pass in {}, or use non-mutating merge which has the same performance issue as described above; (2) the input array needs to have at least 1 element, otherwise you will get nil back instead of an empty hash
|
10

Enumerator includes Enumerable. Since 2.1, Enumerable also has a method #to_h. That's why, we can write :-

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Because #each_slice without block gives us Enumerator, and as per the above explanation, we can call the #to_h method on the Enumerator object.

Comments

8

You could try like this, for single array

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

for array of array

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

1 Comment

Consider the remarks on the splat operator as described in this answer.
7
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

or, if you hate Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

or, if you are a lazy fan of broken functional programming:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

3 Comments

If you don't fully hate Hash[ ... ] but want to use it as a chained method (like you can do with to_h) you can combine Boris suggestions and write: arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
To make the semantics of the code above clearer: This will create a "lazy hash" h, which is initially empty, and will pull out elements from the original array a when needed. Only then will they actually be stored in h!
Don't do all of the suggestions here iterate multiple times through the whole array? This might be a downside.
2

All answers assume the starting array is unique. OP did not specify how to handle arrays with duplicate entries, which result in duplicate keys.

Let's look at:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

You will lose the item 1 => item 2 pair as it is overridden bij item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

All of the methods, including the reduce(&:merge!) result in the same removal.

It could be that this is exactly what you expect, though. But in other cases, you probably want to get a result with an Array for value instead:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

The naïve way would be to create a helper variable, a hash that has a default value, and then fill that in a loop:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

It might be possible to use assoc and reduce to do above in one line, but that becomes much harder to reason about and read.

1 Comment

This seems like a predestined case to apply each_with_object({}) (ruby >= 1.9.1.378) as in this related answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.