Rompiendo de un bucle nested

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

No quiero tener que usar un booleano y luego tengo que decir ir a otro método, sino simplemente ejecutar la primera línea de código después del ciclo 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 arrojarse en condiciones verdaderamente excepcionales, etc. Por lo tanto, no creo que esta solución sea buena desde la perspectiva del rendimiento.

No creo que sea correcto aprovechar las características más nuevas en .NET (métodos anónimos) para hacer algo que es bastante fundamental.

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

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

    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 salir 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 (syntax tbd, etc.) significa que debería funcionar algo así 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"); 

    No sé si funciona en C #, pero en CI a menudo hace esto:

      for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { if (exit_condition) { // cause the outer loop to break: i = INT_MAX; Console.WriteLine("Hi"); // break the inner loop break; } } } 

    Esta solución no se aplica a C #

    Para las personas que encontraron esta pregunta a través de otros idiomas, Javascript, Java y D permiten roturas etiquetadas y continúan :

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

    Use una protección adecuada en el bucle externo. Coloque el protector en el lazo 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 ciclo interno en un método y salga del ciclo 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 cites sobre esto, pero podrías usar goto como se sugiere en MSDN. Hay otras soluciones, como incluir un indicador que se comprueba en cada iteración de ambos bucles. Finalmente, podría usar una excepción como una solución realmente pesada para su problema.

    IR:

     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 for nested en un método privado? De esa forma, simplemente podría ‘regresar’ fuera del método para salir del ciclo.

    factorizar en una función / método y usar el retorno anticipado, o reorganizar los bucles en una cláusula while. goto / exceptions / lo que sea, ciertamente no es apropiado aquí.

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

    Pediste una combinación de rápido, agradable, sin uso de 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.

    Me parece que a la gente no le gusta mucho una statement de goto , así que sentí la necesidad de arreglar esto un poco.

    Creo que las ’emociones’ que la gente tiene sobre goto finalmente se reducen a la comprensión del código y (conceptos erróneos) sobre las posibles implicaciones de rendimiento. Antes de responder la pregunta, primero entraré en algunos detalles sobre cómo se comstack.

    Como todos sabemos, C # se comstack en IL, que luego se comstack en ensamblador utilizando un comstackdor de SSA. Daré un poco de información sobre cómo funciona todo esto, y luego intentaré 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 darte una buena idea de lo que sucede debajo del capó.

    Primera traducción: de foreach al bucle equivalente for (Nota: estoy usando un array 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 

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

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

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

      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 comstackdor hace estas cosas en un solo paso, le da una idea del proceso. El código IL que evoluciona del progtwig 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 ha 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 necesitamos más y más código para obtener lo mismo. También podría argumentar que foreach , for , while y break son en realidad short-hands para goto , lo cual es parcialmente cierto.

    De IL a Ensamblador

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

    Cada comstackdor 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 grafos para analizar twigs, y luego optimizamos cosas como cycli en nuestras twigs (por ejemplo, twigs hacia atrás).

    Sin embargo, ahora tenemos twigs avanzadas para implementar nuestros bucles. Como habrás adivinado, este es realmente 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 twig hacia atrás, que es nuestro pequeño bucle. Lo único que sigue siendo desagradable aquí es la sucursal con la que terminamos debido a nuestra statement de break . En algunos casos, podemos mover esto de la misma manera, pero en otros está ahí para quedarse.

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

    Sin embargo, las twigs tienden a romper esos buenos patrones predecibles, lo cual es algo optimizador, por lo tanto amable, una antipatía. Break, continue, goto, todos intentan romper estos patrones predecibles, y por lo tanto, no son realmente "agradables".

    También debe darse cuenta en este punto de que un foreach simple es más predecible que un montón de declaraciones goto que van por todas partes. En términos de (1) legibilidad y (2) desde la perspectiva del optimizador, es la mejor solución.

    Otra cosa que vale la pena mencionar es que es muy relevante para optimizar los comstackdores para asignar registros a las variables (un proceso llamado asignación de registro ). Como sabrá, solo hay un número finito de registros en su CPU y son, con mucho, las piezas de memoria más rápidas en su hardware. Las variables utilizadas en el código que está 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 sea menor).

    Ayuda, demasiada complejidad ... ¿Qué debería hacer?

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

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

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

    Y ahora llegamos a la verdadera pregunta.

    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 completamente:

     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 puede decidir devolver un booleano desde el bucle (pero personalmente prefiero el formulario SESE porque así es como lo verá el comstackdor y creo que es más fácil leerlo).

    Algunas personas piensan que es más limpio usar una variable temporal y proponer 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 comstack el código. Ahora piense en lo que esto hará con estos patrones agradables y predecibles. ¿Obtener la imagen?

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

    • El comstackdor escribirá todo como twigs.
    • Como paso de optimización, el comstackdor realizará análisis de flujo de datos en un bash de eliminar la extraña variable more que solo se usa en el flujo de control.
    • Si tiene éxito, la variable more será eliminada del progtwig y solo quedarán twigs. Estas twigs se optimizarán, por lo que obtendrá una sola ramificación del lazo interno.
    • Si no es exitoso, la variable more definitivamente se usa en el ciclo más interno, por lo que si el comstackdor no lo optimiza, tiene una alta probabilidad de asignarse a un registro (que consume una valiosa memoria de registro).

    Entonces, para resumir: el optimizador en su comstackdor se va a meter en un infierno de muchos problemas para darse cuenta de que more solo se usa para el flujo de control, y en el mejor de los casos, lo traducirá a una sola twig fuera del exterior en bucle.

    En otras palabras, el mejor escenario posible es que termine con el equivalente a 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 comstackdor como para la legibilidad, y escribamos eso de inmediato.

    tl; dr:

    Línea de fondo:

    • Use una condición simple en su bucle for si es posible. Siga las construcciones lingüísticas de alto nivel que tenga a su scope tanto como sea posible.
    • Si todo falla y te quedas con goto o bool more , prefieres el anterior.

    A veces es bueno abstraer el código en su propia función y luego usar un retorno anticipado; sin embargo, los primeros retornos 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 ciclo interno.

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

    No es elegent, pero puede ser la solución más fácil dependiendo de su problema.

    He visto muchos ejemplos que usan “break” pero ninguno que usa “continue”.

    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 la 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 que, en mis días de estudiante, se decía que es matemáticamente demostrable que puedes hacer cualquier cosa en el código sin un goto (es decir, no hay ninguna situación en la que goto sea la única respuesta). Por lo tanto, nunca uso goto’s (solo mi preferencia personal, sin sugerir que estoy en lo cierto o equivocado)

    De todos modos, para salir de los bucles nesteds 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 ayude a aquellos que me quieren son anti-goto “fanboys” 🙂

    Así es como lo hice. Todavía 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 outter.

    Funciona para for , foreach o while o cualquier clase de bucle y cualquier lenguaje que use try catch exception block

     try { foreach (object o in list) { foreach (object another in otherList) { // ... some stuff here if (condition) { throw new CustomExcpetion(); } } } } catch (CustomException) { // log } 

    Como veo, aceptaste la respuesta en la que la persona te remite a tu statement, donde en la progtwigción moderna y en opinión de expertos, Goto es un asesino, lo llamamos un asesino en progtwigción que tiene ciertas razones, que no voy a discutir aquí 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 simple. 🙂

      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 } } 

    ¿Incluso miraste la palabra clave break ? Oo

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

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

    Si piensas que break es una función como break() , entonces su parámetro sería la cantidad de bucles de los que se saldrá. Como estamos en el tercer ciclo del código aquí, podemos salir de los tres.

    Manual: http://php.net/break

    Creo que a menos que quieras hacer lo “booleano”, la única solución es tirar. ¡Lo cual obviamente no deberías hacer …!