Si j'ai une boucle for qui est imbriquée dans une autre, comment puis-je sortir efficacement des deux boucles (intérieure et extérieure) de la manière la plus rapide possible?
Je ne veux pas avoir à utiliser un booléen et ensuite dire aller à une autre méthode, mais plutôt simplement exécuter la première ligne de code après la boucle externe.
Quelle est une façon rapide et agréable de procéder?
Je pensais que les exceptions ne sont pas bon marché / ne devraient être lancées que dans un état vraiment exceptionnel, etc. Par conséquent, je ne pense pas que cette solution serait bonne du point de vue des performances.
Je ne pense pas qu'il soit juste de profiter des nouvelles fonctionnalités de .NET (méthodes anon) pour faire quelque chose qui est assez fondamental.
la source
Réponses:
Eh bien,
goto
mais c'est moche et pas toujours possible. Vous pouvez également placer les boucles dans une méthode (ou une méthode anon) et utiliserreturn
pour revenir au code principal.contre:
Notez qu'en C # 7, nous devrions obtenir des «fonctions locales», ce qui (syntaxe tbd, etc.) signifie que cela devrait fonctionner quelque chose comme:
la source
goto
c'est dangereux en soi , alors que c'est simplement le bon instrument pour faire certaines choses, et comme de nombreux instruments peuvent être mal utilisés. Cette aversion religieuse contregoto
est franchement assez stupide et certainement non scientifique.Adaptation C # de l'approche souvent utilisée en C - valeur définie de la variable de la boucle externe en dehors des conditions de la boucle (c'est-à-dire que pour une boucle utilisant une variable int
INT_MAX -1
est souvent un bon choix):Comme le dit la note dans le code,
break
ne passera pas par magie à la prochaine itération de la boucle externe - donc si vous avez du code en dehors de la boucle interne, cette approche nécessite plus de vérifications. Envisagez d'autres solutions dans ce cas.Cette approche fonctionne avec
for
etwhile
boucle mais ne fonctionne pas pourforeach
. Dans le cas oùforeach
vous n'auriez pas accès au code de l'énumérateur caché, vous ne pouvez donc pas le changer (et même si vous le pouviez, ilIEnumerator
n'y a pas de méthode "MoveToEnd").Remerciements aux auteurs des commentaires en ligne:
i = INT_MAX - 1
suggestion de Metafor
/foreach
commentaire de ygoe .Proper
IntMax
par jmbpianoremarque sur le code après boucle interne par blizpasta
la source
Cette solution ne s'applique pas à C #
Pour les personnes qui ont trouvé cette question via d'autres langages, Javascript, Java et D autorisent les interruptions étiquetées et continuent :
la source
Utilisez une protection appropriée dans la boucle extérieure. Placez la garde dans la boucle intérieure avant de vous casser.
Ou mieux encore, faites abstraction de la boucle interne dans une méthode et quittez la boucle externe lorsqu'elle renvoie false.
la source
Ne me citez pas à ce sujet, mais vous pouvez utiliser goto comme suggéré dans le MSDN. Il existe d'autres solutions, comme l'inclusion d'un indicateur qui est vérifié à chaque itération des deux boucles. Enfin, vous pouvez utiliser une exception comme solution vraiment lourde à votre problème.
ALLER À:
État:
Exception:
la source
Est-il possible de refactoriser la boucle imbriquée pour une méthode privée? De cette façon, vous pouvez simplement «revenir» hors de la méthode pour quitter la boucle.
la source
[&] { ... return; ... }();
Il me semble que les gens n'aiment pas
goto
beaucoup une déclaration, j'ai donc ressenti le besoin de clarifier un peu les choses.Je crois que les `` émotions '' des gens
goto
se résument finalement à la compréhension du code et aux (idées fausses) sur les implications possibles en termes de performances. Avant de répondre à la question, je vais donc d'abord entrer dans certains détails sur la façon dont il est compilé.Comme nous le savons tous, C # est compilé en IL, qui est ensuite compilé en assembleur à l'aide d'un compilateur SSA. Je vais vous donner un aperçu de la façon dont tout cela fonctionne, puis essayer de répondre à la question elle-même.
De C # à IL
Nous avons d'abord besoin d'un morceau de code C #. Commençons simple:
Je vais faire cette étape par étape pour vous donner une bonne idée de ce qui se passe sous le capot.
Première traduction: de
foreach
à lafor
boucle équivalente (Remarque: j'utilise un tableau ici, car je ne veux pas entrer dans les détails d'IDisposable - auquel cas je devrais également utiliser un IEnumerable):Deuxième traduction: le
for
etbreak
est traduit en un équivalent plus simple:Et troisième traduction (c'est l'équivalent du code IL): on change
break
etwhile
en une branche:Alors que le compilateur fait ces choses en une seule étape, il vous donne un aperçu du processus. Le code IL qui découle du programme C # est la traduction littérale du dernier code C #. Vous pouvez le voir par vous-même ici: https://dotnetfiddle.net/QaiLRz (cliquez sur 'voir IL')
Maintenant, une chose que vous avez observée ici est que pendant le processus, le code devient plus complexe. La façon la plus simple d'observer cela est le fait que nous avions besoin de plus en plus de code pour accomplir la même chose. Vous pourriez arguer que
foreach
,for
,while
etbreak
sont en fait de courtes mains pourgoto
, qui est en partie vrai.De l'IL à l'assembleur
Le compilateur .NET JIT est un compilateur SSA. Je n'entrerai pas dans tous les détails du formulaire SSA ici et comment créer un compilateur d'optimisation, c'est tout simplement trop, mais je peux donner une compréhension de base de ce qui va se passer. Pour une compréhension plus approfondie, il est préférable de commencer à lire sur l'optimisation des compilateurs (j'aime ce livre pour une brève introduction: http://ssabook.gforge.inria.fr/latest/book.pdf ) et LLVM (llvm.org) .
Chaque compilateur d'optimisation repose sur le fait que le code est simple et suit des modèles prévisibles . Dans le cas des boucles FOR, nous utilisons la théorie des graphes pour analyser les branches, puis optimisons des choses comme les cycli dans nos branches (par exemple les branches en arrière).
Cependant, nous avons maintenant des branches avancées pour implémenter nos boucles. Comme vous l'avez peut-être deviné, c'est en fait l'une des premières étapes que le JIT va corriger, comme ceci:
Comme vous pouvez le voir, nous avons maintenant une branche en arrière, qui est notre petite boucle. La seule chose qui est encore méchante ici est la branche avec laquelle nous nous sommes retrouvés en raison de notre
break
déclaration. Dans certains cas, nous pouvons procéder de la même manière, mais dans d'autres, il est là pour rester.Alors pourquoi le compilateur fait-il cela? Eh bien, si nous pouvons dérouler la boucle, nous pourrons peut-être la vectoriser. Nous pourrions même être en mesure de prouver qu'il n'y a que des constantes ajoutées, ce qui signifie que toute notre boucle pourrait disparaître dans l'air. Pour résumer: en rendant les motifs prévisibles (en rendant les branches prévisibles), nous pouvons prouver que certaines conditions tiennent dans notre boucle, ce qui signifie que nous pouvons faire de la magie lors de l'optimisation JIT.
Cependant, les branches ont tendance à briser ces beaux modèles prévisibles, ce qui est quelque chose que les optimiseurs n'apprécient donc pas. Break, continue, goto - ils ont tous l'intention de briser ces modèles prévisibles - et ne sont donc pas vraiment «sympas».
Vous devez également réaliser à ce stade qu'un simple
foreach
est plus prévisible qu'un tas degoto
déclarations qui vont partout. En termes de (1) lisibilité et (2) du point de vue de l'optimiseur, c'est à la fois la meilleure solution.Une autre chose à noter est qu'il est très pertinent d'optimiser les compilateurs pour attribuer des registres aux variables (un processus appelé allocation de registre ). Comme vous le savez peut-être, il n'y a qu'un nombre fini de registres dans votre CPU et ce sont de loin les pièces de mémoire les plus rapides de votre matériel. Les variables utilisées dans le code qui se trouve dans la boucle la plus interne sont plus susceptibles d'obtenir un registre affecté, tandis que les variables en dehors de votre boucle sont moins importantes (car ce code est probablement moins frappé).
Aide, trop de complexité ... que dois-je faire?
L'essentiel est que vous devez toujours utiliser les constructions de langage dont vous disposez, qui construisent généralement (implicitement) des modèles prévisibles pour votre compilateur. Essayez d'éviter les branches étranges si possible ( en particulier:
break
,continue
,goto
oureturn
au milieu de rien).La bonne nouvelle ici est que ces modèles prévisibles sont à la fois faciles à lire (pour les humains) et faciles à repérer (pour les compilateurs).
L'un de ces modèles est appelé SESE, qui signifie Single Entry Single Exit.
Et maintenant, nous arrivons à la vraie question.
Imaginez que vous ayez quelque chose comme ça:
La façon la plus simple d'en faire un modèle prévisible est simplement d'éliminer
if
complètement le:Dans d'autres cas, vous pouvez également diviser la méthode en 2 méthodes:
Variables temporaires? Bon, mauvais ou laid?
Vous pourriez même décider de renvoyer un booléen depuis la boucle (mais je préfère personnellement le formulaire SESE car c'est ainsi que le compilateur le verra et je pense que c'est plus propre à lire).
Certaines personnes pensent qu'il est plus propre d'utiliser une variable temporaire et proposent une solution comme celle-ci:
Je suis personnellement opposé à cette approche. Regardez à nouveau comment le code est compilé. Réfléchissez maintenant à ce que cela fera avec ces jolis motifs prévisibles. Obtenez l'image?
Bon, permettez-moi de l'épeler. Ce qui va arriver, c'est que:
more
variable qui n'est utilisée que dans le flux de contrôle.more
sera supprimée du programme et seules les branches resteront. Ces branches seront optimisées, vous n'obtiendrez qu'une seule branche hors de la boucle interne.more
est définitivement utilisée dans la boucle la plus interne, donc si le compilateur ne l'optimise pas, elle a de grandes chances d'être allouée à un registre (qui consomme une précieuse mémoire de registre).Donc, pour résumer: l'optimiseur de votre compilateur aura beaucoup de mal à comprendre qu'il
more
n'est utilisé que pour le flux de contrôle, et dans le meilleur des cas, il le traduira en une seule branche en dehors de l'extérieur pour boucle.En d'autres termes, le meilleur scénario est qu'il aboutira à l'équivalent de ceci:
Mon opinion personnelle à ce sujet est assez simple: si c'est ce que nous voulions depuis le début, rendons le monde plus facile pour le compilateur et la lisibilité, et écrivons cela tout de suite.
tl; dr:
Conclusion:
goto
ou l' autrebool more
, préférez le premier.la source
yield return
. Autrement dit, bien qu'il soit facile de montrer qu'il existe des cas utiles, pour la plupart des codes "au niveau de l'application", il s'agit probablement d'une très faible occurrence de frustration avec C #, à l'exclusion de ceux "venant tout juste" de C / C ++ ;-) Le "lancer" de Ruby (déroulement sans exception) à l'occasion car il s'inscrit également dans ce domaine.goto
, ne rend pas votre code plus lisible - je dirais que ce qui se passe est exactement le contraire: en fait, votre flux de contrôle contiendra plus de nœuds - ce qui est à peu près la définition de la complexité. Le simple fait de l'éviter pour favoriser l'autodiscipline semble contre-productif en termes de lisibilité.goto
n'ont tout simplement pas la discipline de ne pas en abuser.factoriser dans une fonction / méthode et utiliser un retour anticipé, ou réorganiser vos boucles dans une clause while. goto / exceptions / tout ce qui n'est certainement pas approprié ici.
la source
Vous avez demandé une combinaison de quick, nice, no use of a boolean, no use of goto, and C #. Vous avez exclu toutes les façons possibles de faire ce que vous voulez.
Le moyen le plus rapide et le moins laid est d'utiliser un goto.
la source
Parfois agréable d'abréger le code dans sa propre fonction et d'utiliser un retour anticipé - les premiers retours sont mauvais cependant:)
la source
J'ai vu beaucoup d'exemples qui utilisent "break" mais aucun qui utilise "continue".
Il faudrait toujours un drapeau quelconque dans la boucle interne:
la source
Depuis que j'ai vu
break
en C il y a quelques décennies, ce problème m'a contrarié. J'espérais qu'une amélioration du langage aurait une extension à casser qui fonctionnerait ainsi:la source
Je me souviens de mes jours d'étudiant qu'il a été dit qu'il est mathématiquement prouvable que vous pouvez faire n'importe quoi dans le code sans goto (c'est-à-dire qu'il n'y a pas de situation où goto est la seule réponse). Donc, je n'utilise jamais de goto (juste ma préférence personnelle, ne suggérant pas que j'ai raison ou tort)
Quoi qu'il en soit, pour sortir des boucles imbriquées, je fais quelque chose comme ceci:
... j'espère que cela aide ceux qui m'aiment sont des "fanboys" anti-goto :)
la source
C'est comme ça que je l'ai fait. Encore une solution de contournement.
la source
La façon la plus simple de terminer une double boucle serait de terminer directement la première boucle
la source
Les boucles peuvent être rompues en utilisant des conditions personnalisées dans la boucle, ce qui permet d'avoir du code propre.
Avec ce code, nous avons la sortie suivante:
la source
Une autre option est une fonction anonyme auto-invoquée . Pas de goto, pas d'étiquette, pas de nouvelle variable, pas de nouveau nom de fonction utilisé et une ligne plus courte que l' exemple de méthode anonyme en haut.
la source
Lance une exception personnalisée qui sort de la boucle externe.
Cela fonctionne pour
for
,foreach
ouwhile
ou tout type de boucle et tout langage qui utilise letry catch exception
blocla source
la source
Comme je vois que vous avez accepté la réponse dans laquelle la personne se réfère à votre déclaration goto, où dans la programmation moderne et dans l'opinion d'experts goto est un tueur, nous l'avons appelé un tueur en programmation qui a certaines raisons, que je ne discuterai pas ici à ce stade, mais la solution de votre question est très simple, vous pouvez utiliser un indicateur booléen dans ce type de scénario comme je vais le démontrer dans mon exemple:
simple et simple. :)
la source
Avez-vous même regardé le
break
mot-clé? OoCe n'est qu'un pseudo-code, mais vous devriez pouvoir voir ce que je veux dire:
Si vous pensez
break
être une fonction commebreak()
, alors son paramètre serait le nombre de boucles à sortir. Comme nous sommes dans la troisième boucle du code ici, nous pouvons sortir des trois.Manuel: http://php.net/break
la source
Je pense que si vous ne voulez pas faire la "chose booléenne", la seule solution est en fait de lancer. Ce que vous ne devriez évidemment pas faire ..!
la source