Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails 在散列上注入被认为是好的风格吗?_Ruby On Rails_Ruby - Fatal编程技术网

Ruby on 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 } 我看到的第一个版

在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 }

我看到的第一个版本的唯一优点是,在一个封闭的块中进行操作,并且不(显式地)初始化散列。否则它会意外地滥用一种方法,更难理解和阅读。那么为什么它如此流行呢?

我认为这与人们不完全理解何时使用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)) }