Ruby on rails 在散列上注入被认为是好的风格吗?
在Rails代码内部,人们倾向于使用Ruby on rails 在散列上注入被认为是好的风格吗?,ruby-on-rails,ruby,Ruby On Rails,Ruby,在Rails代码内部,人们倾向于使用Enumerable#inject方法创建哈希,如下所示: somme_enum.inject({}) do |hash, element| hash[element.foo] = element.bar hash end 虽然这似乎已成为一个常见的习语,但有人认为它比“天真”版本有优势吗 hash = {} some_enum.each { |element| hash[element.foo] = element.bar } 我看到的第一个版
Enumerable#inject
方法创建哈希,如下所示:
somme_enum.inject({}) do |hash, element|
hash[element.foo] = element.bar
hash
end
虽然这似乎已成为一个常见的习语,但有人认为它比“天真”版本有优势吗
hash = {}
some_enum.each { |element| hash[element.foo] = element.bar }
我看到的第一个版本的唯一优点是,在一个封闭的块中进行操作,并且不(显式地)初始化散列。否则它会意外地滥用一种方法,更难理解和阅读。那么为什么它如此流行呢?我认为这与人们不完全理解何时使用reduce有关。我同意你的看法,每一件事都应该是这样的情人眼里出西施。那些有函数式编程背景的人可能会更喜欢基于
inject
的方法(就像我一样),因为它与具有相同语义,这是从多个输入计算单个结果的常用方法。如果您了解inject
,那么您应该了解该函数正在按预期使用
作为这个方法看起来更好的一个原因(在我看来),请考虑<代码>哈希< /COD>变量的词法范围。在基于
inject
的方法中,hash
仅存在于块体中。在基于的每个方法中,块内的散列
变量需要与块外定义的某些执行上下文一致。要在同一函数中定义另一个哈希吗?使用inject
方法,可以剪切和粘贴基于inject
的代码并直接使用,而且几乎肯定不会引入错误(忽略编辑过程中是否应该使用C&P,人们会这样做)。使用each
方法,您需要对代码进行C&P,并将hash
变量重命名为您想要使用的任何名称-额外的步骤意味着这更容易出错。inject
(又称reduce
)在函数式编程语言中有着长期而受人尊敬的地位。如果你已经准备好冒险,并且想了解Matz对Ruby的很多灵感,你应该阅读计算机程序的基本结构和解释,可在线访问
一些程序员发现将所有内容都放在一个词汇包中在风格上更简洁。在hash示例中,使用inject意味着您不必在单独的语句中创建空hash。此外,inject语句直接返回结果——您不必记住它在哈希变量中。要让这一点真正清楚,请考虑:
[1, 2, 3, 5, 8].inject(:+)
vs
第一个版本返回总和。第二个版本将总和存储在total
中,作为程序员,您必须记住使用total
而不是语句返回的值
一个小小的附录(纯粹是idomatic——不是关于inject):您的示例可能写得更好:
some_enum.inject({}) {|hash, element| hash.update(element.foo => element.bar) }
…由于hash.update()
返回散列本身,因此不需要在末尾使用额外的hash
语句
更新
@Aleksey羞辱了我,让我对各种组合进行基准测试。请参阅我的基准测试回复。简表:
hash = {}
some_enum.each {|x| hash[x.foo] = x.bar}
hash
是最快的,但可以更优雅地重铸,速度也一样快,如下所示:
我刚找到了一份工作
建议使用而不是inject
:
hash = some_enum.each_with_object({}) do |element, h|
h[element.foo] = element.bar
end
我觉得这很自然
另一种方法,使用:
正如Aleksey指出的,Hash#update()
比Hash#store()
慢,但这让我想到了#inject()
相对于直接的每个循环的总体效率,因此我对一些事情进行了基准测试:
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
结果是:
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
可枚举#每个
都比可枚举#注入
快,散列#存储
比散列#更新
快。但最快的方法是在初始化时传入数组:
Hash[PAIRS]
如果在创建散列后添加元素,则获胜的版本正是OP所建议的:
hash = {}
PAIRS.each {|sym, val| hash[sym] = val }
hash
但是在这种情况下,如果你是一个纯粹主义者,想要一个单一的词汇形式,你可以使用#tap
和#each
并获得相同的速度:
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
对于那些不熟悉tap
的人,它会在主体内创建接收方的绑定(新散列),并最终返回接收方(相同的散列)。如果您了解Lisp,可以将其视为Ruby版本的LET绑定
既然有人问过,下面是测试环境:
# 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
如果要返回散列,使用merge可以使其更干净,这样以后就不必返回散列
some_enum.inject({}){|h,e| h.merge(e.foo => e.bar) }
若您的枚举是散列,那个么可以使用(k,v)很好地获取键和值
为什么?什么时候应该使用或不使用reduce
/inject
/fold
?如果您可以应用一个表达式,将其降为单个值,则应该使用reduce。在本例中,我们正在从一个数据结构转换到另一个数据结构,这并不真正合适,因此示例代码使其适合,本质上将注入转换为each。如果你在最后把一个uniq链起来,你也可以用一张地图来做这件事,但这和试图把一个圆钉子塞进一个方孔是一样的,只是因为你做不好。谢谢你解释你的理由。在我看来,reduce
被定义为接受累加器和一个值,然后返回一个新的累加器,并将该值折叠起来。无论累加器是否为容器类型,或者信息是否在“缩减”过程中丢失,都无关紧要。在Haskell中,列表反转(给定[1,2,3]
获取[3,2,1]
)是根据标准使用折叠操作来实现的。你说得对,累加器的功能是容器类型,这与它无关。然而,我个人认为,将一个n元素容器作为inpu的函数
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
# 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
some_enum.inject({}){|h,e| h.merge(e.foo => e.bar) }
some_hash.inject({}){|h,(k,v)| h.merge(k => do_something(v)) }