Как выйти из вложенных циклов в Ruby?

предположим следующий рубиновый код:

bank.branches do |branch|
  branch.employees.each do |employee|
    NEXT BRANCH if employee.name = "John Doe"
  end
end

NEXT BRANCH — это, конечно, псевдокод. есть ли способ выйти из родительского цикла, как это можно сделать, например, в Perl (используя метки цикла)?

заранее спасибо.


person crlsrns    schedule 13.03.2011    source источник
comment
NEXT BRANCH — очень плохой псевдокод для выхода из родительского цикла. Поскольку next и break являются разными ключевыми словами.   -  person Andrew Marshall    schedule 13.03.2011


Ответы (5)


Поймай и брось может быть тем, что вам нужно:

bank.branches do |branch|
  catch :missingyear do  #:missingyear acts as a label
    branch.employees.each do |employee|
      (2000..2011).each do |year|
        throw :missingyear unless something  #break out of two loops
      end
    end
  end #You end up here if :missingyear is thrown
end
person steenslag    schedule 13.03.2011
comment
Это своего рода label и goto? - person bfontaine; 12.05.2012
comment
Для корректности здесь catch и throw не являются ошибками в Ruby. В Ruby есть raise и rescue для отлова ошибок. поймать и бросить на самом деле просто выйти из контекста. Так же, как goto в некоторых других языках. - person Gurpartap Singh; 14.08.2012

Нет встроенного способа выйти из содержащих блоков без их согласия. Вам просто нужно сделать что-то вроде:

bank.branches do |branch|
  break unless branch.employees.each do |employee|
    break if employee.name == "John Doe"
  end
end
person Chuck    schedule 13.03.2011

Моим импульсом было бы переместить вложенные блоки в метод с return вместо break.

def find_branch_and_employee_by_name(bank,emp_name)
  bank.branches.each do |branch|
    branch.employees.each do |employee|
      return([branch,employee]) if employee.name == emp_name
    end
  end
  nil   # employee wasn't found
end
person Stovey    schedule 02.07.2013

Редактировать: Желаемый эффект, как оказалось, может быть достигнут гораздо проще, если вызвать break внутри внутреннего цикла (только завершит этот цикл):

bank.branches do |branch|
  branch.employees.each do |employee|
    break if employee.name = "John Doe"
  end
end

Вот что писал @steenslag с помощью блока Ruby begin-rescue-end:

letters = [%w(a b c),%w(d e f),%w(g h i)]
# => [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]

letters.each do |trine|
  begin
    trine.each do |letter|
      raise "Breaking out of inner cycle." if letter == "e"
      puts letter
    end
  rescue
    next
  end
end
# => abcdghi

Итак, ваш пример:

bank.branches do |branch|
  branch.employees.each do |employee|
    begin
      raise "Breaking out of inner cycle." if employee.name = "John Doe"
    rescue
      next
    end      
  end
end
person Epigene    schedule 21.10.2014

В других сообщениях упоминается идея, похожая на создание переменной «переключатель». Ниже приведен наглядный пример того, как это работает. Имейте в виду, что второй цикл все еще будет работать, пока не достигнет конца массива сотрудников, но не будет выполнять какой-либо код после того, как переключатель будет переключен. Это не оптимальный способ сделать это, поскольку это может быть излишне затратным по времени, если ваш массив сотрудников велик.

def workforce
  bank.branches do |branch|
    switch = 0
    branch.employees.each do |employee|
      if switch == 1
       next
      end  
      if employee.name = "John Doe"
       switch = 1
      end
   end
 end

После переключения переключателя внутренний массив больше не будет активным, а родительский цикл перейдет к следующей ветви со сбросом переключателя. Очевидно, что для более сложных случаев можно использовать больше переключателей.

person MusicIsFreedom    schedule 06.11.2014