如何在Ruby中打破外部循环?

60
在Perl中,有一种打破外层循环的能力,就像这样:
AAA: for my $stuff (@otherstuff) {
         for my $foo (@bar) {
             last AAA if (somethingbad());
         }
      }

(语法可能有误)该语句使用循环标签在内部循环中跳出外部循环。Ruby 中是否有类似的语句?

8个回答

111

考虑使用throw/catch。通常情况下,下面代码中的外部循环将运行五次,但使用throw,您可以根据需要更改它,同时打破它。请考虑以下完全有效的ruby代码:

catch (:done) do
  5.times { |i|
    5.times { |j|
      puts "#{i} #{j}"
      throw :done if i + j > 5
    }
  }
end

5
个人而言,我不喜欢在正常代码执行中使用异常抛出。这会强制程序员遵循多个逻辑流程。 - Jeff Waltzer
19
我不理解这条评论。在上面的代码片段中,没有任何异常被引发。整个代码只发送了六个消息:catchtimesputsthrow+<=>。我没有使用raise - Jörg W Mittag
21
我猜测在“抛出/捕获”和“引发/救援”(异常)之间存在混淆,它们看起来很相似但实际上不同。 - Kris
10
当程序出错时,使用raise和rescue异常机制可以很好地放弃执行,但有时在正常处理期间跳出深层嵌套的结构也很方便。这就是catch和throw的用处所在。 - Ryenski
1
这是使用throw在满足条件时退出长循环的示例:https://gist.github.com/1120376 - Ryenski
抛出和捕获是一个完美的比喻...这太美妙了。 - Felipe

39
你想要的是非局部控制流,Ruby 有几个选项可以实现:
- Continuations(连续体) - Exceptions(异常) - throw/catch
Continuations:
优点: - Continuations 是非局部控制流的标准机制。实际上,你可以在它们之上构建任何非局部控制流(子例程、过程、函数、方法、协同程序、状态机、生成器、条件、异常):它们几乎是 `GOTO` 的更好版本。
缺点: - Continuations 不是 Ruby 语言规范的强制部分,这意味着一些实现(XRuby、JRuby、Ruby.NET、IronRuby)没有实现它们。因此,你不能依靠它们。
Exceptions:
优点: - 有一篇论文证明了异常在某些情况下比 Continuations 更为强大。换句话说,它们可以做到 Continuations 能做到的一切,而且还能做更多的事情,所以你可以使用它们代替 Continuations。 - 异常是普遍可用的。
缺点: - 它们被称为“异常”,使人们认为它们只适用于“异常情况”。这意味着三件事:阅读你代码的人可能无法理解它,实现可能不会针对它进行优化(是的,在几乎所有 Ruby 实现中,异常非常慢),而且最糟糕的是,你会厌倦所有那些人不停、毫无意义地喋喋不休“异常只适用于异常情况”的说法,一旦他们看了看你的代码。(当然,他们甚至不会尝试理解你在做什么。)
throw/catch:
这大概是它的样子:
catch :aaa do
  stuff.each do |otherstuff|
    foo.each do |bar|
      throw :aaa if somethingbad
    end
  end
end

优点:

  • 与异常相同。
  • 在 Ruby 1.9 中,使用异常进行控制流实际上是语言规范的一部分!循环、枚举器、迭代器等都使用 StopIteration 异常来终止。

缺点:

  • Ruby 社区甚至比使用异常进行控制流更加讨厌它们。

为了2011年的读者着想:http://www.coffeepowered.net/2011/06/17/jruby-performance-exceptions-are-not-flow-control/ 表明JRuby在异常处理方面仍然有些慢。 - Andrew Grimm
2
我从未听说过 Ruby 社区对 throw/catch(甚至是异常)用于控制流的厌恶。他们更喜欢使用 Continuations 吗?你是在 ruby-talk 或其他地方读到这种情绪的吗? - Kelvin

32

没有,不存在。

你的选项包括:

  • 将循环放入一个方法中,并使用return来从外层循环中退出
  • 在内层循环中设置或返回一个标志,然后在外层循环中检查该标志,并在该标志被设置时从外层循环中退出(有点麻烦)
  • 使用throw/catch来退出循环

我喜欢第一和第二个想法。非常干净的解决方案,没有滥用异常。 - Miloš Černilovský

3
while c1
 while c2
    do_break=true
 end
 next if do_break
end

或者根据您的需求选择"break if do_break"


1
也许这就是你想要的?(未经测试)
stuff.find do |otherstuff|
  foo.find do
    somethingbad() && AAA
  end
end

find方法会一直循环,直到块返回一个非空值或列表的末尾被遍历完。


AAA在这里应该是什么?在问题中的Perl示例中,它是一个标签。我不知道在你的回答中它应该是什么。(而且Ruby也不知道,会出现uninitialized constant AAA (NameError)的错误,即使我设置了stufffoosomethingbad,使其对代码的其他部分有意义。)很抱歉,我必须给这个回答点个踩,但如果你能调整一下让它有意义的话,我很乐意改变评价(如果你还在的话)。 - undefined

1
在循环周围包装一个内部方法可能会解决问题 示例:
test = [1,2,3]
test.each do |num|
  def internalHelper
    for i in 0..3 
      for j in 0..3
        puts "this should happen only 3 times"
        if true
          return
        end
      end
    end
  end
internalHelper
end

在任何一个for循环内部,您都可以进行检查,并在满足条件时从内部方法返回。


0
你可以考虑添加一个标志,在内部循环中设置,以控制外部循环。
在外部循环中使用'next'。
for i in (1 .. 5)
  next_outer_loop = false
  for j in (1 .. 5)
    if j > i     
      next_outer_loop = true if j % 2 == 0
      break      
    end          
    puts "i: #{i}, j: #{j}"
  end            
  print "i: #{i} "                                                                                                                                                                             
  if next_outer_loop
    puts "with 'next'"
    next         
  end            
  puts "withOUT 'next'"
end

跳出外层循环

for i in (1 .. 5)
  break_outer_loop = false
  for j in (1 .. 5)
    if j > i
      break_outer_loop = true if i > 3
      break
    end
    puts "i: #{i}, j: #{j}"
  end
  break if break_outer_loop
  puts "i: #{i}"
end

-1

我知道明天早上我会后悔,但简单地使用 while 循环就可以解决问题。

x=0
until x==10
  x+=1
  y=0
  until y==10
    y+=1
    if y==5 && x==3
      x,y=10,10
    end
  end
  break if x==10
  puts x
end

if y==5 && x==3 只是一个表达式为真的示例。


24
现在是早上,请寻找遗憾。 - John F. Miller
是的,我担心这样做有点偏离了重点(至少我是这么理解的),想要退出内循环并进入外循环的下一次迭代(编辑:或者实际上,打破外循环是OP想要的,只是不是我想要的),这是基于内循环代码中间遇到的条件(因此在内循环的条件中不容易检查)。 - undefined

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