Как нарушить внешний цикл в Ruby?

В Perl существует возможность разбить внешний цикл следующим образом:

AAA: for my $stuff (@otherstuff) {
         for my $foo (@bar) {
             last AAA if (somethingbad());
         }
      }

(синтаксис может быть неправильным), который использует метку цикла, чтобы нарушить внешний цикл из внутреннего цикла. Есть ли что-то подобное в Ruby?

Ответ 1

Что вы хотите - это нелокальный поток управления, который Ruby имеет несколько вариантов:

  • Продолжение,
  • Исключения и
  • throw/catch

Продолжения

Плюсы:

  • Продолжение - это стандартный механизм нелокального управления потоком. На самом деле, вы можете построить любой нелокальный поток управления (подпрограммы, процедуры, функции, методы, сопрограммы, государственные машины, генераторы, условия, исключения): они в значительной степени являются более близким двойником GOTO.

Минусы:

  • Продолжения не являются обязательной частью спецификации языка Ruby, что означает, что некоторые реализации (XRuby, JRuby, Ruby.NET, IronRuby) не реализуют их. Таким образом, вы не можете полагаться на них.

Исключения

Плюсы:

  • Существует бумага, которая математически доказывает, что Исключения могут быть более мощными, чем Continuations. IOW: они могут делать все, что могут сделать продолжения, и многое другое, поэтому вы можете использовать их в качестве замены для продолжения.
  • Исключения доступны повсеместно.

Минусы:

  • Они называются "исключениями", которые заставляют людей думать, что они "только для исключительных обстоятельств". Это означает три вещи: кто-то, читающий ваш код, может не понимать его, реализация может быть не оптимизирована для него (и, да, исключения являются довольно медленными почти в любой реализации 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 ненавидит их даже больше, чем использование исключений для потока управления.

Ответ 2

Рассмотрим throw/catch. Обычно внешний цикл в приведенном ниже коде будет выполняться пять раз, но с броском вы можете изменить его на все, что захотите, сломав его в процессе. Рассмотрим этот вполне корректный код ruby:

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

Ответ 3

Нет, нет.

Ваши варианты:

  • поместите цикл в метод и используйте return для выхода из внешнего цикла
  • установить или вернуть флаг из внутреннего цикла, а затем проверить этот флаг во внешнем цикле и выйти из него, когда флаг установлен (что является громоздким)
  • использовать throw/catch для выхода из цикла

Ответ 4

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

или "break if do_break" в зависимости от того, что вы хотите

Ответ 5

Возможно, это то, что вы хотите? (не проверено)

stuff.find do |otherstuff|
  foo.find do
    somethingbad() && AAA
  end
end

Метод find сохраняет цикл до тех пор, пока блок не вернет ненулевое значение или ударит конец списка.

Ответ 6

Я знаю, что буду сожалеть об этом утром, но просто используя цикл 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 - это только пример выражения true.

Ответ 7

Обертка внутреннего метода вокруг петель может сделать трюк Пример:

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 и вернуться из внутреннего метода после выполнения условия.

Ответ 8

Вы можете рассмотреть возможность добавления флага, который установлен во внутреннем цикле, для управления внешним циклом.

'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

'break' внешний цикл

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