Ruby for循环陷阱?

时间:2012-05-01 10:31:45

标签: ruby

在讨论Ruby循环时,Niklas B.最近谈到了循环,而不是引入新的范围',与每个循环相比。我想看一些人们如何看待这个例子。

O.K。,我扩展了一个问题:在Ruby的其他地方我们看到了什么apears做/结束块分隔符,但实际上里面没有范围?还有什么别的......为......做...结束?

O.K。,问题的另一个扩展是,有没有办法用花括号{block}来编写循环?

3 个答案:

答案 0 :(得分:14)

让我们通过一个例子说明这一点:

results = []
(1..3).each do |i|
  results << lambda { i }
end
p results.map(&:call)  # => [1,2,3]

很酷,这是预期的。现在检查以下内容:

results = []
for i in 1..3
  results << lambda { i }
end
p results.map(&:call)  # => [3,3,3]
嗯,发生什么事了?相信我,这些类型的错误是令人讨厌的追踪。 Python或JS开发人员会知道我的意思:))

仅此一点就是我避免像瘟疫这样的循环的原因,尽管有更多有利于这一立场的好论据。正如Ben指出的那样,使用Enumerable中的正确方法几乎总是会产生比使用普通的,命令式for循环或发烧友Enumerable#each更好的代码。例如,上面的例子也可以简洁地写成

lambdas = 1.upto(3).map { |i| lambda { i } }
p lambdas.map(&:call)
  

我扩展了一个问题:在Ruby的其他地方我们看到了什么apears做/结束块分隔符,但实际上里面没有范围?还有什么别的......为......做...结束?

每个循环结构都可以这样使用:

while true do
  #...
end

until false do
  # ...
end

另一方面,我们可以在没有do(这显然是可取的)的情况下编写其中的每一个:

for i in 1..3
end

while true
end

until false
end
  

问题的另一个扩展是,有一种方法可以用花括号{block}

来编写循环

不,没有。另请注意,术语“块”在Ruby中具有特殊含义。

答案 1 :(得分:2)

首先,我将解释为什么你不想使用for,然后解释你为什么会这样做。

您不想使用for的主要原因是它不是惯用的。如果您使用each,则可以轻松地将each替换为mapfindeach_with_index,而无需对代码进行重大更改。但是没有for_mapfor_findfor_with_index

另一个原因是,如果你在each内的一个区块内创建一个变量,并且它之前没有创建过,那么只要该循环存在,它就会一直存在。一旦你没有使用它们就摆脱变量是一件好事。

现在我要提一下为什么你可能想要使用foreach为每个循环创建一个闭包,如果重复该循环次数太多,该循环可能会导致性能问题。在https://stackoverflow.com/a/10325493/38765中,我发布使用while循环而不是块使其变慢。

RUN_COUNT = 10_000_000
FIRST_STRING = "Woooooha"
SECOND_STRING = "Woooooha"

def times_double_equal_sign
  RUN_COUNT.times do |i|
    FIRST_STRING == SECOND_STRING
  end
end

def loop_double_equal_sign
  i = 0
  while i < RUN_COUNT
    FIRST_STRING == SECOND_STRING
    i += 1
  end
end

times_double_equal_sign持续花费2.4秒,而loop_double_equal_sign则持续0.2至0.3秒。

https://stackoverflow.com/a/6475413/38765中,我发现执行空循环需要1.9秒,而执行空块需要5.7秒。

知道为什么你不想使用for,知道为什么要使用for,并且只在需要时使用后者。除非你对其他语言怀有怀旧情绪。 :)

答案 2 :(得分:1)

嗯,在1.9之前的Ruby中,偶数块也不完美。他们并不总是引入新的范围:

i = 0
results = []
(1..3).each do |i|
  results << lambda { i }
end
i = 5
p results.map(&:call)  # => [5,5,5]