212

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.

user664833
  • 18,397
  • 19
  • 91
  • 140
djhworld
  • 6,726
  • 4
  • 30
  • 44
  • Is there a comparisson of the performance of the different approaches? I commented a list of some ideas at [this related question](https://stackoverflow.com/q/13017501/4575793) – Cadoiz Aug 08 '23 at 14:58

9 Answers9

372
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]
Ben Lee
  • 52,489
  • 13
  • 125
  • 145
  • 1
    what does the `*` do in this example? – tester Dec 05 '12 at 19:42
  • 10
    @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 `*`. – Ben Lee Dec 05 '12 at 20:12
  • 1
    @tester Another common use case for splat is to use in reverse, to define methods with unknown number of parameters. E.g `def whatever *args`. You can pass in a list like `whatever(7, 1, 3, 4)`. And as above, `*args` represents a list and `args` represents an array. Since you are passing in a list, you are actually passing in the `*args`. Thus `args` is an array, and what you end up with is `args == [7, 1, 3, 4]`. So you can pass in whatever parameters you want into the method and then just use `args` to access them. – Ben Lee Dec 05 '12 at 20:15
  • 17
    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. – Mike Lewis Feb 28 '13 at 18:48
  • 1
    @MikeLewis, thanks for the caveat. I've added that to my answer (with attribution to you). – Ben Lee Jun 01 '13 at 19:03
  • Another caveat, this method raises an exception if you pass in an array with an odd number of elements. `ArgumentError: odd number of arguments for Hash` – Josh Jun 01 '13 at 19:13
  • 9
    On big data tables you can use `Hash[a.each_slice(2).to_a]`. – Hauleth Jul 22 '13 at 22:42
  • @ŁukaszNiemier, thanks for that. I've edited that into my answer (with attribution to you) – Ben Lee Jul 22 '13 at 22:58
  • 6
    @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). – Ben Lee Mar 13 '14 at 00:02
  • 2
    Now it is possible to do `a.each_slice(2).to_h` (at least in YARV). – Hauleth Jul 18 '14 at 18:14
  • 1
    Simple code sample to see the error when trying to send a huge number of arguments using splat: https://www.ruby-forum.com/topic/1210854 – Pikachu Jul 04 '16 at 21:15
  • I don't think this answer is correct anymore. The linked documentation at the start of the answer says that `to_h` operates on an array of arrays. Running `["item 1", "item 2", "item 3", "item 4"].to_h` results in `TypeError Exception: wrong element type String at 0 (expected array)`. `[["item 1", "item 2"], ["item 3", "item 4"]].to_h` works though. I think @hauleth is right, the correct answer in my testing with Ruby 2.5.3 is `a.each_slice(2).to_h` – Matthew Feb 26 '19 at 23:37
  • Yup, doesn't seem to work anymore. Removed that answer. Left my original hash splat answer since that never stopped working, and was my original answer anyway. – Ben Lee Mar 01 '19 at 18:51
  • Also the `each_slice(2).to_h` method i already had listed in my answer. – Ben Lee Mar 01 '19 at 18:51
134

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}
Jochem Schulenklopper
  • 6,452
  • 4
  • 44
  • 62
  • 3
    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 Dec 22 '14 at 15:39
  • @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]]`). – Jochem Schulenklopper Dec 23 '14 at 18:27
31

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

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Josh
  • 5,631
  • 1
  • 28
  • 54
Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 1
    @Marius: `*arr` converts `arr` into an argument list, so this is calling the `[]` method of Hash with the contents of arr as arguments. – Chuck Jul 17 '13 at 18:21
  • Consider the remarks on the splat operator as described in [this answer](https://stackoverflow.com/a/4028362/4575793). – Cadoiz Aug 08 '23 at 12:45
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 }
Erik Escobedo
  • 2,793
  • 24
  • 42
  • 2
    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. – Erik Escobedo Dec 11 '12 at 07:03
  • 1
    Sorry, I added a spare asterisk. `Hash[arr]` will do the job for you. – Yossi Dec 11 '12 at 07:09
  • 9
    IMHO better solution: Hash[*array.flatten(1)] – guest Mar 07 '13 at 12:27
  • 2
    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. – Boris Stitnicky Jun 01 '13 at 20:28
  • 1
    Now days you can do this short and sweet: `[[1, 2], [3, 4]].to_h` – CTS_AE Mar 24 '20 at 16:30
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}

Karl Glaser
  • 829
  • 2
  • 10
  • 17
  • 1
    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. –  Dec 05 '16 at 14:45
  • You can also do `.reduce(&:merge!)` – Ben Lee Apr 25 '19 at 14:59
  • 2
    `[{a: 1}, {b: 2}].reduce(&:merge!)` evaluates to `{:a=>1, :b=>2}` – Ben Lee Apr 25 '19 at 14:59
  • 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) }`. – Ben Lee Apr 25 '19 at 15:05
  • 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 – Ben Lee Apr 25 '19 at 15:08
  • If you don't want to mutate the element in place, you should consider `each_with_object({})` (ruby >= 1.9.1.378) as in [this related answer](https://stackoverflow.com/a/13017531/4575793) instead of inject/reduce. – Cadoiz Aug 08 '23 at 12:44
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.

Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
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}
Jenorish
  • 1,694
  • 14
  • 19
  • Consider the remarks on the splat operator as described in [this answer](https://stackoverflow.com/a/4028362/4575793). – Cadoiz Aug 08 '23 at 12:49
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"
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • 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] }` – b-studios Aug 17 '15 at 11:56
  • 1
    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! – Daniel Werner Apr 11 '17 at 15:02
  • Don't do all of the suggestions here iterate multiple times through the whole array? This might be a downside. – Cadoiz Aug 08 '23 at 12:47
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.

berkes
  • 26,996
  • 27
  • 115
  • 206
  • This seems like a predestined case to apply `each_with_object({})` (ruby >= 1.9.1.378) as in [this related answer](https://stackoverflow.com/a/13017531/4575793). – Cadoiz Aug 08 '23 at 12:49