2009-08-29 13 views
49

En Perl, hay una capacidad de romper un ciclo exterior como esto:¿Cómo romper el ciclo externo en Ruby?

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

(sintaxis puede ser malo), que utiliza una etiqueta de bucle para romper el bucle exterior desde el interior del bucle interno. ¿Hay algo similar en Ruby?

Respuesta

34

Lo que queremos es no local de control de flujo, que Ruby tiene varias opciones para hacerlo:

  • 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 bonito de GOTO.

Contras:

  • continuaciones no son una parte obligatoria del Rubí especificación del lenguaje, lo que significa que algunas implementaciones (XRuby, JRuby, Ruby.NET, IronRuby) no las aplican. Entonces, no puedes confiar en ellos.

excepciones

Pros:

  • Hay un documento que demuestra matemáticamente que las excepciones pueden ser más poderoso que 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:

  • Ellos son llamados "excepciones" que hace que la gente piensa que son "sólo para 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 godawful lento en casi cualquier implementación de Ruby) y lo peor de todo es que se cansará de todo esas personas constantemente, balbuceando sin pensar "las excepciones son solo para circunstancias excepcionales", tan pronto como miran su código. (Por supuesto, ellos ni siquiera tratar de entender lo que está haciendo.)

throw/catch

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

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

Pros:

  • Igual que las excepciones.
  • En Ruby 1.9, el uso de excepciones para control-flujo es en realidad parte de la especificación de idioma! Bucles, enumeradores, iteradores y todos usan una excepción StopIteration para la terminación.

Contras:

  • La comunidad Ruby los odia aún más que el uso de excepciones para el control de flujo.
+0

Para el beneficio de los lectores de 2011: http://www.coffeepowered.net/2011/06/17/jruby-performance-exceptions-are-not-flow-control/ indica que JRuby todavía es algo lento en Exceptions. –

+2

Nunca he oído hablar de la comunidad ruby ​​que odia 'throw' /' catch' (o incluso excepciones) para el flujo de control. ¿Prefieren las continuaciones? ¿Leíste sobre este sentimiento en ruby-talk o algo así? – Kelvin

27

No, no lo hay.

Las opciones son:

  • poner el lazo en un retorno del método y el uso de romper el bucle exterior
  • establecer o devolver una bandera del bucle interno y luego comprobar que la bandera en el bucle externo y romper con él cuando se establece el indicador (que es una especie de engorroso)
  • uso tiro/catch para salir del bucle
104

Considere 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. Considere 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 
+3

personalmente no me gusta el uso de elevación excepción para la ejecución normal código . Obliga a un programador a seguir múltiples flujos de lógica. –

+18

No entiendo este comentario. En el fragmento de código anterior no hay excepciones planteadas en ningún lado. Solo se envían seis mensajes en todo el código: 'catch',' times', 'puts',' throw', '+' y '<=>'. No envío 'raise' a ninguna parte. –

+19

Supongo que hay una confusión entre raise/rescue (excepciones) y throw/catch, se ven similares pero no lo son. – Kris

0

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 sólo un ejemplo de una expresión girando cierto.

+20

Ahora es de mañana: por favor, arrepiéntete. –

2

Quizás esto es lo que quieres? (No probado) método

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

El hallazgo mantiene en bucle hasta que el bloque devuelve un valor no nulo o el final de la lista es golpeado.

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

o "descanso si do_break" dependiendo de lo que desee

0

Envolver un método interno en torno a 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 verificación en el interior cualquiera de los bucles for y return del método interno una vez que se cumple una condición.

0

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

'siguiente' el bucle exterior

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' el bucle exterior

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 
Cuestiones relacionadas