简体   繁体   English

在Ruby中,如何从数组中生成哈希?

[英]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. 我还有一个函数f ,它将对单个字符串输入执行操作并返回一个值。 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,这样我就可以编写如下代码:

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

Can that be done? 能做到吗?

Say you have a function with a funtastic name: "f" 假设您有一个函数,函数名称为:“ 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: 如评论中所述,Ruby 1.8.7为此引入了更好的语法:

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

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.) (这些发现可能与基于Ruby版本,奇怪的缓存等的发现并不完全相同,但是总体结果将是相似的。)

arr is a collection of ActiveRecord objects. arr是ActiveRecord对象的集合。

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 基准@ 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 基准@ 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 基准@ 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 基准@ 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. 仅仅因为Ruby具有表现力和动态性,并不意味着您应该始终寻求最漂亮的解决方案。 The basic each loop was the fastest in creating a hash. 基本的每个循环在创建哈希中最快。

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

This is what I would probably write: 我可能会这样写:

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

Simple, clear, obvious, declarative. 简单,清晰,明显,说明性。 What more could you want? 你还能想要什么?

Ruby 2.6.0通过将一个块传递给to_h方法来实现较短的语法:

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

I'm doing it like described in this great article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array 我正在按照这篇出色的文章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)) }

More info about inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject 有关inject方法的更多信息: http : //ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject

Another one, slightly clearer IMHO - 另一个,恕我直言-

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

Using length as f() - 将长度用作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). 除了弗拉多·辛格(Vlado Cingel)的答案(我还不能添加评论,所以我添加了答案)。

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 }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM