Ruby 中循环的首选方式是什么?

9
为什么在Ruby中更偏向于使用each循环而不是for循环?它们的时间复杂度有区别吗,还是只是语法上的差异?

这应该被关闭为重复问题,而不是基于观点的问题。在我看来,第二句清楚地说明了第一句中“首选”一词的标准。虽然我相信肯定有一个类似的问题存在。 - Jörg W Mittag
这根本不需要关闭。SO不是维基百科。我们可以有两个或七个讨论密切相关问题的线程。对于您在此处非常高的资历地位,我们表示最真诚的尊重。 - Boris Stitnicky
5个回答

8

是的,这两种迭代方式是不同的,但希望这个计算能有所帮助。

require 'benchmark'

a = Array( 1..100000000 )
sum = 0
Benchmark.realtime {
  a.each { |x| sum += x }
}

这需要5.866932秒

a = Array( 1..100000000 )
sum = 0
Benchmark.realtime {
  for x in a
    sum += x
  end
}

这需要6.146521秒。

虽然这不是一个正确的基准测试方法,但也有其他一些限制。但在单台计算机上,每个似乎比for循环快一点。


你确定这些基准测试在统计上是可靠的吗?因为 for 被展开成 each,我不认为它们之间会有什么区别。如果有的话,for 应该会稍微快一点,因为它不需要分配一个新的局部作用域,而在 for 循环内声明的变量会污染外部作用域,而传递给 each 的块具有自己的作用域。 - Jörg W Mittag
@JörgWMittag,什么? for 真的是 each 的语法糖吗?而且,顺便说一下,出于同样的原因,我也希望 for 更快。我解释了相反的观察结果,Ruby 开发人员更注重优化 each 而不是 for - Boris Stitnicky
1
@BorisStitnicky:是的,当然。毕竟它没有其他太多的解糖方式。请参阅ISO Ruby语言规范的第11.5.2.3.4节。for var1,var2 in expression; do_something end被评估为expression.each do |var1,var2| do_something end,除了该块没有自己的作用域。尝试def (a = Object.new).each; p 'each called'; yield 42 end; for e in a; p e end # 'each called' # 42 # => 42 - Jörg W Mittag
谢谢,知道幕后发生了什么是很好的。 - Boris Stitnicky

4
  • The variable referencing an item in iteration is temporary and does not have significance outside of the iteration. It is better if it is hidden from outside of the iteration. With external iterators, such variable is located outside of the iteration block. In the following, e is useful only within do ... end, but is separated from the block, and written outside of it; it does not look easy to a programmer:

    for e in [:foo, :bar] do
      ...
    end
    

    With internal iterators, the block variable is defined right inside the block, where it is used. It is easier to read:

    [:foo, :bar].each do |e|
      ...
    end
    
  • This visibility issue is not just for a programmer. With respect to visibility in the sense of scope, the variable for an external iterator is accessible outside of the iteration:

    for e in [:foo] do; end
    e # => :foo
    

    whereas in internal iterator, a block variable is invisible from outside:

    [:foo].each do |e|; end
    e # => undefined local variable or method `e'
    

    The latter is better from the point of view of encapsulation.

  • When you want to nest the loops, the order of variables would be somewhat backwards with external iterators:

    for a in [[:foo, :bar]] do
      for e in a do
        ...
      end
    end
    

    but with internal iterators, the order is more straightforward:

    [[:foo, :bar]].each do |a|
      a.each do |e|
        ...
      end
    end
    
  • With external iterators, you can only use hard-coded Ruby syntax, and you also have to remember the matching between the keyword and the method that is internally called (for calls each), but for internal iterators, you can define your own, which gives flexibility.


3

2
一个有趣的问题。在Ruby中有几种循环的方式。我已经注意到,在Ruby中有一个设计原则,当有多种做同一件事情的方法时,它们之间通常存在微妙的差异,并且每种情况都有自己独特的用途和解决问题的方法。因此,最终你需要能够编写(而不仅仅是阅读)所有这些方法。
至于关于for循环的问题,这与我的早期问题类似,即<my earlier question whethe for loop is a trap>。
基本上有两种明显的循环方式,一种是通过迭代器(或更一般地说,块)进行循环,例如:
[1, 2, 3].each { |e| puts e * 10 }
[1, 2, 3].map { |e| e * 10 )
# etc., see Array and Enumerable documentation for more iterator methods.

与这种迭代方式相连的是类Enumerator,您应该努力理解它。
另一种方式是使用whileuntilfor循环的Pascal式循环。
for y in [1, 2, 3]
  puts y
end

x = 0
while x < 3
  puts x; x += 1
end

# same for until loop

ifunless一样,whileuntil也有它们的尾部形式,例如:
a = 'alligator'
a.chop! until a.chars.last == 'g'
#=> 'allig'

第三种非常重要的循环方式是隐式循环,或者通过递归进行循环。Ruby非常灵活,所有类都是可修改的,可以为各种事件设置钩子,并且可以利用这一点来产生最不寻常的循环方式。可能性是如此无限,以至于我甚至不知道从哪里开始谈论它们。也许一个好的起点是由著名艺术家Yusuke Endoh创建的博客blog by Yusuke Endoh,他使用Ruby代码作为自己选择的艺术材料。
为了说明我的意思,考虑这个循环。
class Object
  def method_missing sym
    s = sym.to_s
    if s.chars.last == 'g' then s else eval s.chop end
  end
end

alligator
#=> "allig"

1

除了可读性问题外,for循环在Ruby领域中迭代,而each从本地代码中进行迭代,因此原则上当迭代数组中的所有元素时,each应更有效率。

使用each循环:

arr.each {|x| puts x}

使用for循环进行循环:

for i in 0..arr.length
   puts arr[i]
end

在每种情况下,我们只是将一个代码块传递给机器的本地代码(快速代码)实现的方法,而在for循环中,所有代码都必须被解释并运行,考虑到Ruby语言的所有复杂性。
然而,for循环比each更灵活,可以以更复杂的方式进行迭代,例如使用给定步骤进行迭代。
编辑
我没有发现您可以在调用each()之前使用step()方法跨越范围,因此我声称的for循环的灵活性实际上是不合理的。

1
你能举一个复杂循环的例子,其中使用foreach更合适吗? - Stefan
嗯,我在想实际上可以使用step()方法来进行步进,然后调用each()。在这种情况下,“for”仍然是一种糟糕的循环方式:D - Claudi
顺便提一下,等价的“for”循环将是“for x in arr; puts x; end”。 - Stefan
我知道,但我使用那种语法是为了展示使用for循环的缺点,特别是如果你来自低级语言的世界:传统的使用for可能会使你的代码高度低效。 - Claudi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接