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 remember the results in a hash.

I know that I can do the desired hash with something like this:

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

What I would like to do is not initialize h, so I can just write something like this:

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

Can this be done?

+70
arrays ruby hash
May 31 '10 at 12:12
source share
6 answers

Say you have a function with the highest name: "f"

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

will provide you with:

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

Updated:

As mentioned in the comments, Ruby 1.8.7 provides a stronger syntax for this:

 h = Hash[arr.collect { |v| [v, f(v)] }] 
+119
May 31 '10 at 12:25
source share

There were some quick, dirty tests for some of these answers. (These results may not be exactly the same with your version of Ruby, weird caching, etc., but the overall 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

Finally

Just because Ruby is expressive and dynamic does not mean that you should always look for the most suitable solution. The main loop was the fastest in creating a hash.

+52
Jan 15 '15 at 11:10
source share
 h = arr.each_with_object({}) { |v,h| h[v] = f(v) } 
+32
Jul 01 2018-12-12T00:
source share

Here is what I will probably write:

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

Simple, clear, obvious, declarative. What else you need?

+11
May 31 '10 at 2:52 p.m.
source share

I do this as described in this wonderful article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array

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

Additional information on the inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject

+5
Nov 28 '13 at 18:30
source share

Another, 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 > 
+1
Dec 10 '14 at 3:11
source share



All Articles