Cómo romper el ciclo exterior en Ruby?

En Perl, existe la capacidad de romper un ciclo externo como este:

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

(la syntax puede ser incorrecta), que utiliza una etiqueta de bucle para romper el bucle externo desde el interior del bucle. ¿Hay algo similar en Ruby?

Lo que desea es flujo de control no local, que Ruby tiene varias opciones para hacer:

  • Continuaciones,
  • Excepciones, y
  • throw / catch

Continuaciones

Pros:

  • Las continuas son el mecanismo estándar para el flujo de control no local. De hecho, puedes construir cualquier flujo de control no local (subrutinas, procedimientos, funciones, métodos, corutinas, máquinas de estado, generadores, condiciones, excepciones) encima de ellos: son más o menos el gemelo más agradable de GOTO .

Contras:

  • Las continuas no son una parte obligatoria de Ruby Language Specification, lo que significa que algunas implementaciones (XRuby, JRuby, Ruby.NET, IronRuby) no las implementan. Entonces, no puedes confiar en ellos.

Excepciones

Pros:

  • Hay un documento que demuestra matemáticamente que las Excepciones pueden ser más poderosas que las Continuaciones. IOW: pueden hacer todo lo que las continuaciones pueden hacer, y más, para que pueda usarlos como reemplazo de las continuaciones.
  • Las excepciones están disponibles universalmente.

Contras:

  • Se llaman “excepciones”, lo que hace que las personas piensen que son “solo por circunstancias excepcionales”. Esto significa tres cosas: alguien que lea su código podría no entenderlo, la implementación podría no estar optimizada para eso (y, sí, las excepciones son tontas, lentas en casi cualquier implementación de Ruby) y lo peor de todo es que se cansará de todas esas personas constantemente, balbuceando sin pensar “las excepciones son solo para circunstancias excepcionales”, tan pronto como echen un vistazo a su código. (Por supuesto, ni siquiera tratarán de entender lo que estás haciendo).

throw / catch

Esto es (más o menos) lo que se vería:

 catch :aaa do stuff.each do |otherstuff| foo.each do |bar| throw :aaa if somethingbad end end end 

Pros:

  • Lo mismo que las excepciones.
  • En Ruby 1.9, ¡el uso de excepciones para control-flow es en realidad parte de la especificación del lenguaje ! Bucles, enumeradores, iteradores y todos usan una excepción StopIteration para la terminación.

Contras:

  • La comunidad de Ruby los odia incluso más que el uso de excepciones para control-flujo.

Considera throw / catch . Normalmente, el ciclo externo del código siguiente se ejecutará cinco veces, pero con throw puedes cambiarlo a lo que quieras, rompiéndolo en el proceso. Considera este código ruby ​​perfectamente válido:

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

No, no hay.

Tus opciones son:

  • poner el lazo en un método y usar retorno para romper desde el bucle externo
  • establecer o devolver una bandera desde el bucle interno y luego verificar esa bandera en el bucle externo y romperla cuando se establece la bandera (lo cual es algo engorroso)
  • usa throw / catch para salir del lazo
 while c1 while c2 do_break=true end next if do_break end 

o “romper si do_break” dependiendo de lo que quieras

Quizás esto es lo que quieres? (no probado)

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

El método de búsqueda sigue dando vueltas hasta que el bloque devuelve un valor no nulo o se golpea el final de la lista.

Sé que me arrepentiré de esto en la mañana, pero el simple hecho de usar un ciclo while podría hacer el truco.

 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 

El if y==5 && x==3 es solo un ejemplo de una expresión que se convierte en verdadera.

Envolver un método interno alrededor de los bucles podría hacer el truco Ejemplo:

 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 

Aquí puede hacer una comprobación dentro de cualquiera de los bucles for y regresar desde el método interno una vez que se cumple una condición.

Puede considerar agregar un indicador, que se establece dentro del bucle interno, para controlar el bucle externo.

‘siguiente’ el bucle externo

 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 

‘romper’ el bucle externo

 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