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?
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.
c#
for-loop
nested-loops
GurdeepS
fuente
fuente
Respuestas:
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 anon) y usarreturn
para volver al código principal.vs:
Tenga en cuenta que en C # 7 deberíamos obtener "funciones locales", que (sintaxis tbd, etc.) significa que debería funcionar algo como:
fuente
goto
es dañino per se , mientras que simplemente es el instrumento correcto para hacer algunas cosas, y como muchos instrumentos pueden ser mal utilizados. Esta aversión religiosa contragoto
es francamente bastante estúpida y definitivamente no científica.Adaptación de C # del enfoque que se usa a menudo en C: establecer el valor de la variable del bucle externo fuera de las condiciones del bucle (es decir, para el bucle que usa la variable int a
INT_MAX -1
menudo es una buena opción):Como dice la nota en el código
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
for
ywhile
bucles pero no funcionaforeach
. En caso deforeach
que no tenga acceso de código al enumerador oculto, no podrá cambiarlo (e incluso si no pudieraIEnumerator
tener algún método "MoveToEnd").Agradecimientos a los autores de comentarios en línea:
i = INT_MAX - 1
sugerencia de Metafor
/foreach
comentario de ygoe .Comentario apropiado
IntMax
de jmbpianosobre el código después del bucle interno por blizpasta
fuente
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 interrupciones etiquetadas y continúa :
fuente
Use un protector adecuado en el bucle exterior. Coloque la guardia en el bucle interno antes de romper.
O mejor aún, abstraiga el bucle interno en un método y salga del bucle externo cuando devuelva falso.
fuente
No me cites sobre esto, pero podrías 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.
IR:
Condición:
Excepción:
fuente
¿Es posible refactorizar el bucle anidado en un método privado? De esa manera, simplemente podría 'regresar' del método para salir del bucle.
fuente
[&] { ... return; ... }();
Me parece que a la gente no le gusta mucho una
goto
declaración, así que sentí la necesidad de aclarar esto un poco.Creo que las 'emociones' que las personas tienen sobre el
goto
futuro se reducen a la comprensión del código y (conceptos erróneos) sobre las 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:
Lo haré paso a paso para darle una buena idea de lo que sucede debajo del capó.
Primera traducción: desde
foreach
alfor
bucle equivalente (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):Segunda traducción: el
for
ybreak
se traduce a un equivalente más fácil:Y tercera traducción (este es el equivalente del código IL): cambiamos
break
ywhile
en una rama:Si bien el compilador hace estas cosas en un solo paso, le brinda información sobre el 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
,for
,while
ybreak
son realmente cortos-manos paragoto
, lo cual es cierto en parte.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, utilizamos 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 solucionará, como este:
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
break
declaración. 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 agradables y predecibles, que es algo optimizadores, por lo tanto, un disgusto. Romper, continuar, ir a, todos tienen la intención de romper estos patrones predecibles y, por lo tanto, no son realmente "agradables".
También debe darse cuenta en este punto de que un simple
foreach
es más predecible que un montón degoto
declaraciones que van por todas partes. 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 las 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. Trate de evitar las ramas extrañas si es posible (en concreto:
break
,continue
,goto
oreturn
en el medio de la 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 verdadera pregunta.
Imagina que tienes algo como esto:
La forma más fácil de hacer de este un patrón predecible es simplemente eliminar por
if
completo:En otros casos, también puede dividir el método en 2 métodos:
Variables temporales? ¿Bueno, malo o feo?
Incluso podría decidir devolver un booleano desde dentro del ciclo (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:
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:
more
variable extraña que solo se usa en el flujo de control.more
será eliminada del programa y solo quedarán ramas. Estas ramas se optimizarán, por lo que obtendrá solo una rama del bucle interno.more
definitivamente se usa en el bucle más interno, por lo que si el compilador no la optimiza, tiene una alta probabilidad de ser asignada a un registro (que consume memoria de registro valiosa).Entonces, para resumir: el optimizador en su compilador tendrá muchos problemas para descubrir que
more
solo se usa para el flujo de control, y en el mejor de los casos, lo traducirá a una sola rama fuera del exterior para lazo.En otras palabras, el mejor de los casos es que terminará con el equivalente de esto:
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 de fondo:
goto
obool more
, prefiere el primero.fuente
yield return
. Es decir, si bien es fácil mostrar que hay algunos casos útiles, para la mayoría de los códigos de "nivel de aplicación" es probable que se produzca una frustración muy baja con C #, excluyendo aquellos "recién provenientes de" C / C ++ ;-) También ocasionalmente extraño El "lanzamiento" de Ruby (desenrollado sin excepción) en ocasiones, ya que también cabe en este dominio.goto
, no hace que su código sea más legible. Diría que lo que sucede es exactamente lo contrario: de hecho, su flujo de control contendrá más nodos, lo cual es bastante La definición de complejidad. Simplemente evitarlo para ayudar a la autodisciplina suena contraproducente en términos de legibilidad.goto
simplemente no tienen la disciplina para no abusar de él.factorizar en una función / método y usar retorno temprano, o reorganizar sus bucles en una cláusula while. goto / excepciones / lo que sea, ciertamente no es apropiado aquí.
fuente
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.
fuente
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:)
fuente
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:
fuente
Desde la primera vez que vi
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í:fuente
Recuerdo de mis días de estudiante que se dijo que es matemáticamente demostrable 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:
... espero que eso ayude a aquellos que como yo somos "fanboys" anti-goto :)
fuente
Así lo hice yo. Sigue siendo una solución alternativa.
fuente
La forma más fácil de finalizar un doble ciclo sería terminar directamente el primer ciclo
fuente
Los bucles se pueden romper utilizando condiciones personalizadas en el bucle, lo que permite tener un código limpio.
Con este código tenemos el siguiente resultado:
fuente
Otra opción es una función anónima auto invocada . Sin goto, sin etiqueta, sin nueva variable, sin nombre de función nueva y una línea más corta que el ejemplo de método anónimo en la parte superior.
fuente
Lanza una excepción personalizada que salga del bucle externo.
Funciona para
for
,foreach
o parawhile
cualquier tipo de bucle y cualquier lenguaje que usetry catch exception
bloquefuente
fuente
Como veo, usted aceptó la respuesta en la que 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 asesino en la programació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:
Simple y llano. :)
fuente
¿Incluso miraste la
break
palabra clave? OoEsto es solo un pseudocódigo, pero debería poder ver lo que quiero decir:
Si piensas en
break
ser una función comobreak()
, 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
fuente
Creo que, a menos que quieras hacer lo "booleano", la única solución es lanzar. ¡Lo que obviamente no deberías hacer ...!
fuente