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

在Ruby中,如何从数组生成哈希?,ruby,arrays,hash,Ruby,Arrays,Hash,我有一个简单的数组: arr = ["apples", "bananas", "coconuts", "watermelons"] 我还有一个函数f,它将对单个字符串输入执行操作并返回一个值。这个操作非常昂贵,所以我想在散列中记录结果 我知道我可以用这样的东西来制作所需的散列: h = {} arr.each { |a| h[a] = f(a) } h = arr.(???) { |a| a => f(a) } 我想做的是不必初始化h,这样我就可以这样写: h = {} arr.ea

我有一个简单的数组:

arr = ["apples", "bananas", "coconuts", "watermelons"]
我还有一个函数
f
,它将对单个字符串输入执行操作并返回一个值。这个操作非常昂贵,所以我想在散列中记录结果

我知道我可以用这样的东西来制作所需的散列:

h = {}
arr.each { |a| h[a] = f(a) }
h = arr.(???) { |a| a => f(a) }
我想做的是不必初始化h,这样我就可以这样写:

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

可以这样做吗?

假设你有一个函数,名字很有趣:“f”

将为您提供:

{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
更新:

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

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

我可能会这样写:

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

简单、清晰、明显、声明性。你还想要什么呢?

我正在按照这篇伟大的文章中所描述的那样做

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

关于
inject
方法的更多信息:

另一个,稍微清晰一点的IMHO-

Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Hash[*array.reduce([]){| memo,水果| memo[“苹果”、“香蕉”、“椰子”、“西瓜”]
2.1.5:027>散列[*array.reduce([]){| memo,水果| memo 6,“香蕉”=>7,“椰子”=>8,“西瓜”=>11}
2.1.5 :028 >

在给出的一些答案上做了一些快速、肮脏的基准测试。(这些发现可能与您基于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.850000000000005、@total=0.8500000000000005

基准测试@real=0.74612、@cstime=0.0、@cutime=0.0、@stime=0.010000000000000009、@utime=0.740000000000002、@total=0.750000000000002

基准@real=0.627355、@cstime=0.0、@cutime=0.0、@stime=0.010000000000000009、@utime=0.61999999974、@total=0.629999999975

基准@real=1.650568、@cstime=0.0、@cutime=0.0、@stime=0.12999999998、@utime=1.51、@total=1.64

总之
仅仅因为Ruby具有表现力和动态性,并不意味着您应该总是选择最漂亮的解决方案。基本的each循环是创建哈希的最快方法。

Ruby 2.6.0通过以下方式实现了更短的语法:


除了Vlado Cingel的答案(我还不能添加评论,所以我添加了一个答案)

Inject也可以这样使用:块必须返回累加器。只有块中的赋值返回赋值的值,并报告错误

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

我想你的意思是
。{v,f(v)]}
,但这确实奏效了!只有一件事-为什么
*
旁边有一个
*
?@Jeriko-splat操作符
*
根据上下文将列表收集到数组中或将数组展开到列表中。这里它将数组展开到列表中(用作新散列的项目)。在看了Jörg的答案并仔细考虑之后,请注意,您可以删除
*
flatten
以获得更简单的版本:
h=hash[arr.collect{v,f(v)]]
。但是,我不确定是否有我看不到的问题。在Ruby 1.8.7中,丑陋的
散列[*key\u pairs.flatten]
只是
散列[key\u pairs]
。更好,如果你还没有从1.8.6更新,那么
需要“backport”
。我和下一个家伙一样喜欢
zip
,但是既然我们已经在调用
map
,为什么不把它放在这里?
h=Hash[arr.map{v,f(v)]]
你的版本有我看不到的优势吗?@Telemachus:我读了所有Haskell代码,我只是习惯了点自由编程,仅此而已。这比使用Hash[arr.collect{…}要简洁得多这是难以置信的慢,看看我下面的帖子:你,我的朋友,做你的家庭作业和张贴它是很棒的:)使用手动递增的循环变量稍微快一点:我没有你的数据集——我只是用@id访问器制作了一个简单的对象,或多或少地匹配了你的数字——但是直接迭代减少了两个百分点分配散列,因为我喜欢封装的块。我对
arr.map
arr.collect
之间的区别有点困惑,因为
.collect
只是
的别名。map
我对这两个版本进行了基准测试:使用merge将执行时间加倍。上面的注入版本是一个可比的版本对于microspino的collect版本,它对迭代的每个步骤都执行
merge
。merge是O(n),迭代也是如此。因此,这是
O(n^2)
而问题本身显然是线性的。就绝对值而言,我只是在一个包含100k个元素的数组上尝试了这个方法,它花费了
730秒
,而这个线程中提到的其他方法花费了
0.7
1.1秒
。是的,这是一个减速系数700!
Benchmark.measure { 
    100000.times {
        h = Hash[arr.collect { |v| [v.id, v] }]
    }
}
Benchmark.measure {
    100000.times {
        hash = {}
        arr.each { |a| hash[a.id] = a }
    }
}
Benchmark.measure {
    100000.times {
        arr.each_with_object({}) { |v, h| h[v.id] = v }
    }
}
arr.to_h { |a| [a, f(a)] }
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }