Как сломать внешний цикл в Ruby? - PullRequest
53 голосов
/ 29 августа 2009

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

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

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

Ответы [ 8 ]

105 голосов
/ 29 августа 2009

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

catch (:done) do
  5.times { |i|
    5.times { |j|
      puts "#{i} #{j}"
      throw :done if i + j > 5
    }
  }
end
36 голосов
/ 30 августа 2009

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

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

Продолжения

Плюсы:

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

Минусы:

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

Исключения

Плюсы:

  • Есть статья, которая математически доказывает, что исключения могут быть более мощными, чем продолжения. 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 ненавидит их даже больше, чем использование исключений для потока управления.
29 голосов
/ 29 августа 2009

Нет, нет.

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

  • поместите цикл в метод и используйте return для выхода из внешнего цикла
  • установить или вернуть флаг из внутреннего цикла, а затем проверить этот флаг во внешнем цикле и отключить его, когда флаг установлен (что довольно громоздко)
  • используйте команду throw / catch для выхода из цикла
4 голосов
/ 29 сентября 2012
while c1
 while c2
    do_break=true
 end
 next if do_break
end

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

2 голосов
/ 30 августа 2009

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

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

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

0 голосов
/ 21 декабря 2016

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

«следующий» внешний цикл

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
0 голосов
/ 01 апреля 2014

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

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 голосов
/ 29 августа 2009

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

...