In Ruby, How Do I Make a Hash from an Array?

I have a simple array:

arr = ["apples", "bananas", "coconuts", "watermelons"]

I also have a function f that will perform an operation on a single string input and return a value. This operation is very expensive, so I would like to memoize the results in the hash.

I know I can make the desired hash with something like this:

h = {}
arr.each { |a| h[a] = f(a) }

What I'd like to do is not have to initialize h, so that I can just write something like this:

h = arr.(???) { |a| a => f(a) }

Can that be done?

8 Answers

Say you have a function with a funtastic name: "f"

def f(fruit)
   fruit + "!"
end

arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]

will give you:

{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}

Updated:

As mentioned in the comments, Ruby 1.8.7 introduces a nicer syntax for this:

h = Hash[arr.collect { |v| [v, f(v)] }]
8

Did some quick, dirty benchmarks on some of the given answers. (These findings may not be exactly identical with yours based on Ruby version, weird caching, etc. but the general results will be similar.)

arr is a collection of ActiveRecord objects.

Benchmark.measure {
    100000.times {
        Hash[arr.map{ |a| [a.id, a] }]
    }
}

Benchmark @real=0.860651, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.8500000000000005, @total=0.8500000000000005

Benchmark.measure { 
    100000.times {
        h = Hash[arr.collect { |v| [v.id, v] }]
    }
}

Benchmark @real=0.74612, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.740000000000002, @total=0.750000000000002

Benchmark.measure {
    100000.times {
        hash = {}
        arr.each { |a| hash[a.id] = a }
    }
}

Benchmark @real=0.627355, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.6199999999999974, @total=0.6299999999999975

Benchmark.measure {
    100000.times {
        arr.each_with_object({}) { |v, h| h[v.id] = v }
    }
}

Benchmark @real=1.650568, @cstime=0.0, @cutime=0.0, @stime=0.12999999999999998, @utime=1.51, @total=1.64

In conclusion

Just because Ruby is expressive and dynamic, doesn't mean you should always go for the prettiest solution. The basic each loop was the fastest in creating a hash.

3
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
2

Ruby 2.6.0 enables a shorter syntax by passing a block to the to_h method:

arr.to_h { |a| [a, f(a)] }
0

This is what I would probably write:

h = Hash[arr.zip(arr.map(&method(:f)))]

Simple, clear, obvious, declarative. What more could you want?

2

I'm doing it like described in this great article

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }

More info about inject method:

1

Another one, slightly clearer IMHO -

Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]

Using length as f() -

2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
 => ["apples", "bananas", "coconuts", "watermelons"] 
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
 => {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11} 
2.1.5 :028 >

in addition to the answer of Vlado Cingel (I cannot add a comment yet, so I added an answer).

Inject can also be used in this way: the block has to return the accumulator. Only the assignment in the block returns the value of the assignment, and an error is reported.

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }
1
Sarah Jenkins

Sarah Jenkins

Senior Technology Editor & AI Specialist

Sarah Jenkins is a veteran tech journalist with over 12 years of experience covering artificial intelligence, mobile innovations, and digital ethics. Her insights have appeared in leading technology publications worldwide.

Share this article