ave*_*ell 27 ruby ruby-on-rails
在Rails代码中,人们倾向于使用Enumerable #injection方法来创建哈希,如下所示:
somme_enum.inject({}) do |hash, element|
hash[element.foo] = element.bar
hash
end
Run Code Online (Sandbox Code Playgroud)
虽然这似乎已成为一种常见的习语,但是有没有人看到优于"天真"版本的优势,这将是:
hash = {}
some_enum.each { |element| hash[element.foo] = element.bar }
Run Code Online (Sandbox Code Playgroud)
我在第一个版本中看到的唯一优势是你在一个封闭的块中执行它并且你没有(显式地)初始化哈希.否则它会以一种意想不到的方式滥用方法,难以理解并且难以阅读.那为什么它如此受欢迎?
fea*_*ool 30
正如Aleksey所指出的,Hash#update()比Hash#store()慢,但这让我想到了#inject()与直接#each循环的整体效率.所以我对一些事情进行了基准测试:
(注意:2012年9月19日更新,包括#each_with_object)
(注意:2014年3月31日更新,包括#by_initialization,感谢/sf/users/17147861/的建议 )
require 'benchmark'
module HashInject
extend self
PAIRS = 1000.times.map {|i| [sprintf("s%05d",i).to_sym, i]}
def inject_store
PAIRS.inject({}) {|hash, sym, val| hash[sym] = val ; hash }
end
def inject_update
PAIRS.inject({}) {|hash, sym, val| hash.update(val => hash) }
end
def each_store
hash = {}
PAIRS.each {|sym, val| hash[sym] = val }
hash
end
def each_update
hash = {}
PAIRS.each {|sym, val| hash.update(val => hash) }
hash
end
def each_with_object_store
PAIRS.each_with_object({}) {|pair, hash| hash[pair[0]] = pair[1]}
end
def each_with_object_update
PAIRS.each_with_object({}) {|pair, hash| hash.update(pair[0] => pair[1])}
end
def by_initialization
Hash[PAIRS]
end
def tap_store
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
end
def tap_update
{}.tap {|hash| PAIRS.each {|sym, val| hash.update(sym => val)}}
end
N = 10000
Benchmark.bmbm do |x|
x.report("inject_store") { N.times { inject_store }}
x.report("inject_update") { N.times { inject_update }}
x.report("each_store") { N.times {each_store }}
x.report("each_update") { N.times {each_update }}
x.report("each_with_object_store") { N.times {each_with_object_store }}
x.report("each_with_object_update") { N.times {each_with_object_update }}
x.report("by_initialization") { N.times {by_initialization}}
x.report("tap_store") { N.times {tap_store }}
x.report("tap_update") { N.times {tap_update }}
end
end
Run Code Online (Sandbox Code Playgroud)
Rehearsal -----------------------------------------------------------
inject_store 10.510000 0.120000 10.630000 ( 10.659169)
inject_update 8.490000 0.190000 8.680000 ( 8.696176)
each_store 4.290000 0.110000 4.400000 ( 4.414936)
each_update 12.800000 0.340000 13.140000 ( 13.188187)
each_with_object_store 5.250000 0.110000 5.360000 ( 5.369417)
each_with_object_update 13.770000 0.340000 14.110000 ( 14.166009)
by_initialization 3.040000 0.110000 3.150000 ( 3.166201)
tap_store 4.470000 0.110000 4.580000 ( 4.594880)
tap_update 12.750000 0.340000 13.090000 ( 13.114379)
------------------------------------------------- total: 77.140000sec
user system total real
inject_store 10.540000 0.110000 10.650000 ( 10.674739)
inject_update 8.620000 0.190000 8.810000 ( 8.826045)
each_store 4.610000 0.110000 4.720000 ( 4.732155)
each_update 12.630000 0.330000 12.960000 ( 13.016104)
each_with_object_store 5.220000 0.110000 5.330000 ( 5.338678)
each_with_object_update 13.730000 0.340000 14.070000 ( 14.102297)
by_initialization 3.010000 0.100000 3.110000 ( 3.123804)
tap_store 4.430000 0.110000 4.540000 ( 4.552919)
tap_update 12.850000 0.330000 13.180000 ( 13.217637)
=> true
Run Code Online (Sandbox Code Playgroud)
可枚举#each比Enumerable #inject更快,而Hash #store比Hash #renew更快.但最快的是在初始化时传递一个数组:
Hash[PAIRS]
Run Code Online (Sandbox Code Playgroud)
如果您在创建哈希后添加元素,则获胜版本正是OP所建议的:
hash = {}
PAIRS.each {|sym, val| hash[sym] = val }
hash
Run Code Online (Sandbox Code Playgroud)
但在这种情况下,如果你是一个想要单一词汇形式的纯粹主义者,你可以使用#tap和#each并获得相同的速度:
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
Run Code Online (Sandbox Code Playgroud)
对于不熟悉tap的人,它会在主体内部创建接收器(新散列)的绑定,最后返回接收器(相同的散列).如果您了解Lisp,请将其视为Ruby的LET绑定版本.
-whew-.谢谢收听.
既然人们问过,这里是测试环境:
# Ruby version ruby 2.0.0p247 (2013-06-27) [x86_64-darwin12.4.0]
# OS Mac OS X 10.9.2
# Processor/RAM 2.6GHz Intel Core i7 / 8GB 1067 MHz DDR3
Run Code Online (Sandbox Code Playgroud)
Aid*_*lly 23
美在旁观者的眼中.具有一些函数式编程背景的人可能更喜欢inject基于-Based的方法(就像我一样),因为它具有与fold高阶函数相同的语义,这是从多个输入计算单个结果的常用方法.如果您了解inject,那么您应该了解该功能正在按预期使用.
作为这种方法看起来更好的一个原因(在我看来),考虑hash变量的词法范围.在inject基于-Based的方法中,hash仅存在于块的主体内.在each基于方法的方法中,hash块内的变量需要与块外部定义的某些执行上下文一致.想在同一个函数中定义另一个哈希吗?使用该inject方法,可以剪切并粘贴inject基于代码的代码并直接使用它,并且几乎肯定不会引入错误(忽略在编辑期间是否应该使用C&P - 人们这样做).使用该each方法,您需要C&P代码,并将hash变量重命名为您想要使用的任何名称 - 额外的步骤意味着这更容易出错.
fea*_*ool 10
inject(aka reduce)在函数式编程语言中有着悠久而受人尊敬的地方.如果你准备花了一大笔钱,想明白了很多的Matz的灵感为Ruby的,你应该阅读开创性的计算机程序的结构与解释,可在网上http://mitpress.mit.edu/sicp/.
一些程序员发现在一个词法包中包含所有东西,风格更清晰.在哈希示例中,使用注入意味着您不必在单独的语句中创建空哈希.更重要的是,inject语句直接返回结果 - 您不必记住它在哈希变量中.为了清楚地说明这一点,请考虑:
[1, 2, 3, 5, 8].inject(:+)
Run Code Online (Sandbox Code Playgroud)
VS
total = 0
[1, 2, 3, 5, 8].each {|x| total += x}
Run Code Online (Sandbox Code Playgroud)
第一个版本返回总和.第二个版本存储总和total,作为程序员,您必须记住使用total而不是.each语句返回的值.
一个小小的附录(纯粹是自然的 - 不是关于注入):你的例子可能写得更好:
some_enum.inject({}) {|hash, element| hash.update(element.foo => element.bar) }
Run Code Online (Sandbox Code Playgroud)
...因为hash.update()返回哈希本身,你最后不需要额外的hash语句.
@Aleksey让我陷入了各种组合的基准测试.请参阅我在其他地方的基准回复.简写:
hash = {}
some_enum.each {|x| hash[x.foo] = x.bar}
hash
Run Code Online (Sandbox Code Playgroud)
是最快的,但可以更优雅地重铸 - 而且速度一样快 - 如:
{}.tap {|hash| some_enum.each {|x| hash[x.foo] = x.bar}}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
33731 次 |
| 最近记录: |