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


76

我有一个简单的数组:

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

我也有一个函数f,它将对单个字符串输入执行一个操作并返回一个值。该操作非常昂贵,因此我想在哈希中记住结果。

我知道我可以通过以下方式进行所需的哈希:

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

我想做的就是不必初始化h,这样我就可以编写如下代码:

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

能做到吗?

Answers:


128

假设您有一个函数,函数名称为:“ f”

def f(fruit)
   fruit + "!"
end

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

会给你:

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

更新:

如评论中所述,Ruby 1.8.7为此引入了更好的语法:

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

我想你的意思是... { |v| [v, f(v)] },但这确实成功了!
Wizzlewott

3
只是一件事-为什么在*旁边*arr.collect呢?
杰里科

3
@Jeriko-splat运算符*根据上下文将列表收集到数组中或将数组展开为列表。在这里它将数组展开为一个列表(用作新哈希的项)。
Telemachus,2010年

2
在查看了Jörg的答案并进行了更多思考之后,请注意,您可以同时删除它们*flatten一个更简单的版本:h = Hash[ arr.collect { |v| [ v, f(v) ] } ]。但是,我不确定是否有我看不到的陷阱。
Telemachus,2010年

3
在Ruby 1.8.7中,丑陋Hash[*key_pairs.flatten]就是Hash[key_pairs]。更好,require 'backports'如果您尚未从1.8.6更新。
马克-安德烈·Lafortune

55

对某些给出的答案做了一些快速,肮脏的基准测试。(这些发现可能与基于Ruby版本,奇怪的缓存等的发现并不完全相同,但是总体结果将是相似的。)

arr 是ActiveRecord对象的集合。

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

基准@ 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] }]
    }
}

基准@ 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 }
    }
}

基准@ 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 }
    }
}

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

结论

仅仅因为Ruby具有表现力和动态性,并不意味着您应该始终寻求最漂亮的解决方案。基本的每个循环在创建哈希中最快。


7
您,我的朋友,为您完成功课并将其发布非常好:)
Alexander Bird

使用手动递增的循环变量的速度略快:我没有您的数据集-我只是用@id访问器烹饪了一个琐碎的对象,或多或少地与您的数字匹配了-但直接迭代节省了%的费用。从风格上讲,我更喜欢{} .tap {| h | ....}分配哈希,因为我喜欢封装的块。
android.weasel



11

我可能会这样写:

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

简单,清晰,明显,说明性。你还能想要什么?


1
我和zip下一个家伙一样喜欢,但是既然我们已经在打电话map,为什么不留下这个呢?h = Hash[ arr.map { |v| [ v, f(v) ] } ]我的版本没有您的版本优势吗?
Telemachus,2010年

@Telemachus:读完所有的Haskell代码后,我才习惯于进行无点编程。
约尔格W¯¯米塔格

5

我正在按照这篇出色的文章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)) }

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


merge对迭代的每个步骤都执行一次。合并为O(n),迭代也是如此。因此,此时O(n^2)问题本身显然是线性的。绝对来说,我只是在一个包含100k个元素的数组上进行了尝试,但花了很多时间730 seconds,而该线程中提到的其他方法则花费了从0.7to到任何地方1.1 seconds。是的,这是因子700的减慢速度!
Matthias Winkelmann,

1

另一个,恕我直言-

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

将长度用作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

除了弗拉多·辛格(Vlado Cingel)的答案(我还不能添加评论,所以我添加了答案)。

注入也可以以这种方式使用:块必须返回累加器。仅块中的分配返回分配的值,并报告错误。

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

我对这两个版本进行了基准测试:合并的使用使执行时间加倍。以上注入版本是microspino收集版本的比较
ruud
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.