Pregunta

Si tengo un bucle for que está anidado dentro de otro, ¿cómo puedo salir de ambos bucles (interno y externo) de la manera más rápida posible?

No quiero tener que usar un booleano y luego decir ir a otro método, sino simplemente ejecutar la primera línea de código después del bucle externo.

¿Cuál es una manera rápida y agradable de hacer esto?

Gracias


Estaba pensando que las excepciones no son baratas / solo deberían lanzarse en una condición verdaderamente excepcional, etc. Por lo tanto, no creo que esta solución sea buena desde una perspectiva de rendimiento.

No creo que sea correcto aprovechar las nuevas funciones de .NET (métodos anon) para hacer algo que es bastante fundamental.

Por eso, tvon (¡lo siento, no puedo deletrear nombre de usuario completo!) tiene una buena solución.

Marc: Buen uso de métodos anon, y esto también es genial, pero debido a que podría estar en un trabajo donde no usamos una versión de .NET / C # que admita métodos anon, también necesito conocer un enfoque tradicional .

¿Fue útil?

Solución

Bueno, goto , pero eso es feo y no siempre es posible. También puede colocar los bucles en un método (o un método anónimo) y usar return para volver al código principal.

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

vs:

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

Tenga en cuenta que en C # 7 deberíamos obtener '' funciones locales '', que (sintaxis tbd, etc.) significa que debería funcionar algo como:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

Otros consejos

La adaptación de C # del enfoque que se usa a menudo en C - establece el valor de la variable del bucle externo fuera de las condiciones del bucle (es decir, para el bucle que usa la variable int INT_MAX -1 a menudo es una buena opción):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

Como la nota en el código dice break no saltará mágicamente a la próxima iteración del bucle externo, por lo que si tiene código fuera del bucle interno, este enfoque requiere más comprobaciones. Considere otras soluciones en tal caso.

Este enfoque funciona con para y while , pero no funciona para foreach . En el caso de foreach no tendrá acceso de código al enumerador oculto, por lo que no puede cambiarlo (e incluso si pudiera IEnumerator no tiene algunos " MoveToEnd " método).

Agradecimientos a los autores de comentarios incluidos:
i = INT_MAX - 1 sugerencia de Meta
para / foreach comentario por ygoe .
IntMax adecuado por jmbpiano Comentario sobre el código después del bucle interno por blizpasta

Esta solución no se aplica a C #

Para las personas que encontraron esta pregunta en otros idiomas, Javascript, Java y D permiten interrupciones etiquetadas y continúan :

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

Use un protector adecuado en el bucle exterior. Coloque la protección en el bucle interno antes de romper.

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

O mejor aún, abstraiga el bucle interno en un método y salga del bucle externo cuando devuelva falso.

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

No me cite sobre esto, pero podría usar goto como se sugiere en el MSDN. Hay otras soluciones, como incluir un indicador que se verifica en cada iteración de ambos bucles. Finalmente, podría usar una excepción como una solución realmente pesada para su problema.

GOTO:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

Condición:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

Excepción:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

¿Es posible refactorizar el bucle anidado para un método privado? De esa manera, simplemente podría 'regresar' del método para salir del bucle.

Me parece que a la gente no le gusta mucho una declaración goto , así que sentí la necesidad de aclarar esto un poco.

Creo que las 'emociones' que las personas tienen sobre goto eventualmente se reducen a la comprensión del código y (conceptos erróneos) sobre posibles implicaciones de rendimiento. Antes de responder la pregunta, por lo tanto, primero entraré en algunos de los detalles sobre cómo se compila.

Como todos sabemos, C # se compila en IL, que luego se compila en ensamblador utilizando un compilador SSA. Daré un poco de información sobre cómo funciona todo esto, y luego trataré de responder la pregunta en sí.

De C # a IL

Primero necesitamos una pieza de código C #. Comencemos simple:

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

Haré esto paso a paso para darle una buena idea de lo que sucede debajo del capó.

Primera traducción: de foreach al bucle equivalente for (Nota: estoy usando una matriz aquí, porque no quiero entrar en detalles de IDisposable - en cuyo caso también tendría que usar un IEnumerable):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

Segunda traducción: el para y break se traduce en un equivalente más fácil:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

Y tercera traducción (este es el equivalente del código IL): cambiamos break y while en una rama:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

Si bien el compilador hace estas cosas en un solo paso, le da una idea del proceso. El código IL que evoluciona del programa C # es la traducción literal del último código C #. Puede verlo usted mismo aquí: https://dotnetfiddle.net/QaiLRz (haga clic en 'ver IL')

Ahora, una cosa que has observado aquí es que durante el proceso, el código se vuelve más complejo. La forma más fácil de observar esto es por el hecho de que necesitábamos más y más código para lograr lo mismo. También podría argumentar que foreach , para , while y break son realmente manos cortas para goto , que es parcialmente cierto.

De IL a ensamblador

El compilador .NET JIT es un compilador SSA. No entraré en todos los detalles del formulario SSA aquí y cómo crear un compilador de optimización, es demasiado, pero puedo dar una comprensión básica sobre lo que sucederá. Para una comprensión más profunda, es mejor comenzar a leer sobre la optimización de compiladores (me gusta este libro para una breve introducción: http://ssabook.gforge.inria.fr/latest/book.pdf ) y LLVM (llvm.org).

Cada compilador de optimización se basa en el hecho de que el código es fácil y sigue patrones predecibles . En el caso de los bucles FOR, usamos la teoría de gráficos para analizar ramas y luego optimizamos cosas como cycli en nuestras ramas (por ejemplo, ramas hacia atrás).

Sin embargo, ahora tenemos ramas hacia adelante para implementar nuestros bucles. Como habrás adivinado, este es en realidad uno de los primeros pasos que el JIT va a solucionar, como este:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

Como puede ver, ahora tenemos una rama hacia atrás, que es nuestro pequeño bucle. Lo único que sigue siendo desagradable aquí es la rama con la que terminamos debido a nuestra declaración break . En algunos casos, podemos mover esto de la misma manera, pero en otros está ahí para quedarse.

Entonces, ¿por qué el compilador hace esto? Bueno, si podemos desenrollar el ciclo, podríamos ser capaces de vectorizarlo. Incluso podríamos ser capaces de probar que solo se agregan constantes, lo que significa que todo nuestro circuito podría desaparecer en el aire. Para resumir: al hacer que los patrones sean predecibles (al hacer que las ramas sean predecibles), podemos probar que ciertas condiciones se mantienen en nuestro bucle, lo que significa que podemos hacer magia durante la optimización JIT.

Sin embargo, las ramas tienden a romper esos patrones predecibles agradables, que es algo optimizadores, por lo tanto, un disgusto. Romper, continuar, goto, todos tienen la intención de romper estos patrones predecibles, y por lo tanto no son realmente "agradables".

También debería darse cuenta en este punto de que un simple foreach es más predecible que un montón de declaraciones goto que se extienden por todos lados. En términos de (1) legibilidad y (2) desde una perspectiva optimizadora, es la mejor solución.

Otra cosa que vale la pena mencionar es que es muy relevante para optimizar los compiladores para asignar registros a variables (un proceso llamado asignación de registros ). Como sabrás, solo hay un número finito de registros en tu CPU y son, con mucho, las piezas de memoria más rápidas en tu hardware. Las variables utilizadas en el código que se encuentra en el bucle más interno tienen más probabilidades de obtener un registro asignado, mientras que las variables fuera de su bucle son menos importantes (porque este código probablemente se golpea menos).

Ayuda, demasiada complejidad ... ¿qué debo hacer?

La conclusión es que siempre debe usar las construcciones de lenguaje que tiene a su disposición, que generalmente (implícitamente) crearán patrones predecibles para su compilador. Intente evitar ramas extrañas si es posible (específicamente: break , continue , goto o un return en el medio de nada).

La buena noticia aquí es que estos patrones predecibles son fáciles de leer (para humanos) y fáciles de detectar (para compiladores).

Uno de esos patrones se llama SESE, que significa Single Entry Single Exit.

Y ahora llegamos a la pregunta real.

Imagina que tienes algo como esto:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

La forma más fácil de hacer de este un patrón predecible es simplemente eliminar el if por completo:

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

En otros casos, también puede dividir el método en 2 métodos:

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

¿Variables temporales? ¿Bueno, malo o feo?

Incluso podría decidir devolver un booleano desde dentro del bucle (pero personalmente prefiero el formulario SESE porque así es como lo verá el compilador y creo que es más limpio de leer).

Algunas personas piensan que es más limpio usar una variable temporal y proponen una solución como esta:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

Personalmente me opongo a este enfoque. Mire nuevamente cómo se compila el código. Ahora piense en lo que esto hará con estos patrones agradables y predecibles. ¿Obtener la imagen?

Bien, déjame explicarlo. Lo que sucederá es que:

  • El compilador escribirá todo como ramas.
  • Como paso de optimización, el compilador realizará un análisis de flujo de datos en un intento de eliminar la extraña variable more que solo se usa en el flujo de control.
  • Si tiene éxito, la variable more se eliminará del programa y solo quedarán ramas. Estas ramas se optimizarán, por lo que obtendrá solo una rama del bucle interno.
  • Si no tiene éxito, la variable more se usa definitivamente en el bucle más interno, por lo que si el compilador no lo optimiza, tiene una alta probabilidad de ser asignado a un registro (que consume valiosa memoria de registro).

Entonces, para resumir: el optimizador en su compilador se meterá en muchos problemas para descubrir que more solo se usa para el flujo de control, y en el mejor escenario de caso lo traducirá a una sola rama fuera del bucle externo para.

En otras palabras, el mejor escenario es que terminará con el equivalente de esto:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

Mi opinión personal sobre esto es bastante simple: si esto es lo que pretendíamos desde el principio, hagamos el mundo más fácil tanto para el compilador como para la legibilidad, y escribamos eso de inmediato.

tl;dr:

Línea inferior:

  • Use una condición simple en su ciclo for si es posible. Apéguese a las construcciones de lenguaje de alto nivel que tiene en yo

factoriza en una función / método y usa return temprano, o reorganiza tus bucles en una cláusula while. goto / excepciones / lo que sea que ciertamente no sea apropiado aquí.

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

Usted solicitó una combinación de rápido, agradable, sin uso de un booleano, sin uso de goto y C #. Has descartado todas las formas posibles de hacer lo que quieres.

La forma más rápida y menos fea es usar un goto.

A veces es bueno abstraer el código en su propia función y luego usar un retorno temprano; sin embargo, los retornos tempranos son malos:)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

Dependiendo de su situación, es posible que pueda hacer esto, pero solo si no está ejecutando el código DESPUÉS del bucle interno.

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        i = 100;
        break;
    }
}

No es elegante, pero puede ser la solución más fácil según su problema.

He visto muchos ejemplos que usan " break " pero ninguno que use " continuar " ;.

Todavía requeriría una bandera de algún tipo en el bucle interno:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

Desde que vi por primera vez break en C hace un par de décadas, este problema me ha molestado. Esperaba que alguna mejora del lenguaje tuviera una extensión para romper que funcionaría así:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

Recuerdo de mis días de estudiante que se dijo que es matemáticamente comprobable que puedes hacer cualquier cosa en código sin un goto (es decir, no hay una situación en la que goto sea la única respuesta). Por lo tanto, nunca uso goto's (solo mi preferencia personal, sin sugerir que tengo razón o no)

De todos modos, para salir de los bucles anidados, hago algo como esto:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... espero que sea de ayuda para aquellos que como yo somos anti-goto '' fanboys '' :)

Así lo hice. Sigue siendo una solución.

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}

Lanza una excepción personalizada que sale del bucle externo.

Funciona para para , foreach o while o cualquier tipo de bucle y cualquier lenguaje que use try catch excepcion block

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}
         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

Como veo, usted aceptó la respuesta en la cual la persona lo remite a la declaración goto, donde en la programación moderna y en la opinión de expertos goto es un asesino, lo llamamos un asesino en la programación que tiene ciertas razones, que no discutiré en este punto, pero la solución de su pregunta es muy simple, puede usar una bandera booleana en este tipo de escenario como lo demostraré en mi ejemplo:

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

simple y llano. :)

¿Incluso miró la palabra clave break ? O.o

Esto es solo un pseudocódigo, pero debería poder ver lo que quiero decir:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

Si piensa que break es una función como break () , entonces su parámetro sería el número de bucles para romper. Como estamos en el tercer bucle en el código aquí, podemos separarnos de los tres.

Manual: http://php.net/break

Creo que a menos que quieras hacer la "cosa booleana" la única solución es en realidad tirar. ¡Lo que obviamente no deberías hacer ...!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top