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


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

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

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

8 52

8 ответов:

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

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

продолжения

плюсы:

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

плюсы:

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

исключения

плюсы:

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

плюсы:

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

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

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

нет, нет.

ваши возможности:

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

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

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

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

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

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

The if y==5 && x==3 - это только пример выражения, которое становится истинным.

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

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

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

"далее" внешний цикл

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