Sortir d'une boucle imbriquée


216

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.


Je voulais juste m'assurer: pourquoi voulez-vous faire ça?
Jon Limjap

3
Pourquoi ne voulez-vous pas utiliser un booléen? Qu'est-ce qui ne va pas avec ça?
Anthony

Dans VB.net, vous pouvez enrouler une instruction try / finally (pas de capture) autour d'un nombre arbitraire de boucles, puis "exit try" les quittera toutes à tout moment.
Brain2000

Réponses:


206

Eh bien, gotomais c'est moche et pas toujours possible. Vous pouvez également placer les boucles dans une méthode (ou une méthode anon) et utiliser returnpour revenir au code principal.

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

contre:

// 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");

Notez qu'en C # 7, nous devrions obtenir des «fonctions locales», ce qui (syntaxe tbd, etc.) signifie que cela devrait fonctionner quelque chose comme:

// 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");

49
Dans ce type de situation, je ne pense pas que l'utilisation de goto soit pire que l'utilisation normale de quelque chose comme break (après tout, ce ne sont que des branches inconditionnelles vers une étiquette, c'est juste qu'avec break, l'étiquette est implicite).
Greg Beech

37
goto est parfois moins mal que les alternatives
seanb

7
@BeowulfOF - break ne sortira que de la boucle intérieure, pas des boucles intérieure et extérieure.
Greg Beech

36
Goto lui-même n'est pas laid. Ce qui est moche, c'est abuser de goto qui se traduit par du code spaghetti. Utiliser goto pour sortir de la boucle imbriquée est parfaitement correct. En outre, notez que tout casser, continuer et revenir, du point de vue de la programmation structurelle, n'est guère mieux que goto - fondamentalement, c'est la même chose, juste dans un emballage plus agréable. C'est pourquoi les langages structurels purs (comme le Pascal original) manquent des trois.
el.pescado

11
@ el.pescado a tout à fait raison: de nombreux codeurs ont été induits en erreur en pensant que gotoc'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 contre gotoest franchement assez stupide et certainement non scientifique.
o0 '.

96

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 -1est souvent un bon choix):

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    
}

Comme le dit la note dans le code, breakne 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 foret whileboucle mais ne fonctionne pas pour foreach. Dans le cas où foreachvous 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, il IEnumeratorn'y a pas de méthode "MoveToEnd").

Remerciements aux auteurs des commentaires en ligne:
i = INT_MAX - 1suggestion de Meta
for / foreachcommentaire de ygoe .
Proper IntMaxpar jmbpiano
remarque sur le code après boucle interne par blizpasta


@DrG: ne fonctionnera pas en c # car "l'instruction break termine la boucle englobante la plus proche ou l'instruction switch dans laquelle elle apparaît." (msdn)
blizpasta

1
@blizpasta Alors? S'il rend la condition sur la boucle externe fausse (comme il l'a fait), elle quittera les deux.
Patrick

51
vous devez utiliser i = INT_MAX - 1; sinon i ++ == INT_MIN <100 et la boucle continuera
Meta

6
une idée sur foreach?
ktutnik

4
@ktutnik Cela ne fonctionnera pas avec foreach car vous n'aurez pas accès au code de l'énumérateur caché. De plus, IEnumerator n'a pas de méthode "MoveToEnd".
ygoe

39

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 :

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

40
c'est triste, cela ne peut pas être fait avec c #, cela produirait en tant de fois un code plus propre.
Rickard

17
En fait, je suis devenu excité pendant une seconde jusqu'à ce que je réalise que ce n'était PAS pour c #. :(
Arvo Bowen

1
Ce concept s'applique également à PowerShell au cas où quelqu'un rencontrerait le problème là-bas. (Ils mettent juste les deux points devant le nom de l'étiquette.) Maintenant je sais que ce n'est pas spécifique à PowerShell ... Cette syntaxe serait incompatible avec les étiquettes goto disponibles en C #. PHP utilise autre chose: break 3; Mettez le nombre de niveaux après l'instruction break.
ygoe

1
C'est Java pas c #
Luca Ziegler

Pour ceux qui souhaitent voir cette fonctionnalité en C #, placez vos compliments ici, s'il vous plaît 😉 github.com/dotnet/csharplang/issues/869#issuecomment-326606003
m93a

28

Utilisez une protection appropriée dans la boucle extérieure. Placez la garde dans la boucle intérieure avant de vous casser.

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

Ou mieux encore, faites abstraction de la boucle interne dans une méthode et quittez la boucle externe lorsqu'elle renvoie false.

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

    .... some outer loop stuff

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

    ... more outer loop stuff
}

4
Sauf que l'OP a dit "je ne veux pas avoir à utiliser un booléen".
LeopardSkinPillBoxHat

Très Pascal-ish ... Je préfère probablement utiliser un goto, bien que je les évite généralement comme la peste.
Jonathan Leffler

21

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

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

État:

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

Exception:

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

2
ce sont toutes des solutions de contournement hacky où il serait super propre de simplement intégrer une méthode et d'utiliser un retour anticipé
Dustin Getz

3
:) à droite, c'est une solution simple, mais vous devrez passer toutes les données locales requises dans la méthode comme arguments ... C'est l'un des rares endroits où goto pourrait être la solution appropriée
David Rodríguez - dribeas

La méthode Condition ne fonctionne même pas car "plus de code" sera exécuté une fois après avoir quitté la boucle interne avant de quitter la boucle externe. La méthode GOTO fonctionne mais fait exactement ce que l'affiche dit qu'elle ne veut pas faire. La méthode Exception fonctionne mais est plus laide et plus lente que GOTO.
Programmeur Windows

Je voudrais souligner ce point-virgule après l'étiquette. De cette façon, cette étiquette peut être même à la fin d'un bloc. +1
Tamas Hegedus

@Windowsprogrammer L'OP a demandé: "Quelle est une manière rapide et agréable de procéder?" Goto est la solution préférée: propre et concis, sans impliquer une méthode distincte.
Suncat2000

16

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.


Avec l'avantage secondaire de raccourcir votre méthode originale :-)
Martin Capodici

Les lambdas C ++ 11 facilitent la tâche dans certains cas:[&] { ... return; ... }();
BCS

14

Il me semble que les gens n'aiment pas gotobeaucoup une déclaration, j'ai donc ressenti le besoin de clarifier un peu les choses.

Je crois que les `` émotions '' des gens gotose 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:

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

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à la forboucle é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):

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

Deuxième traduction: le foret breakest traduit en un équivalent plus simple:

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

Et troisième traduction (c'est l'équivalent du code IL): on change breaket whileen une branche:

    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; 

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, whileet breaksont en fait de courtes mains pour goto, 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:

    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:
    // ...

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 breakdé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 foreachest plus prévisible qu'un tas de gotodé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, gotoou returnau 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:

// 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 façon la plus simple d'en faire un modèle prévisible est simplement d'éliminer ifcomplètement le:

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

Dans d'autres cas, vous pouvez également diviser la méthode en 2 méthodes:

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

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

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:

  • Le compilateur écrira tout sous forme de branches.
  • En tant qu'étape d'optimisation, le compilateur effectuera une analyse du flux de données dans le but de supprimer l'étrange morevariable qui n'est utilisée que dans le flux de contrôle.
  • En cas de succès, la variable moresera 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.
  • En cas d'échec, la variable moreest 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 moren'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:

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:

// ...

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:

  • Utilisez une condition simple dans votre boucle for si possible. Tenez-vous autant que possible aux constructions linguistiques de haut niveau dont vous disposez.
  • Si tout échoue et que vous vous retrouvez avec l'un gotoou l' autre bool more, préférez le premier.

OTOH: Je n'ai que rarement le désir de rompre les boucles externes ou le souhait de goto-equiv (et beaucoup de code qui pourrait sans doute être écrit plus proprement), bien que cela puisse être plus courant dans d'autres domaines ... aujourd'hui était un tel cas, mais cela était largement dû à 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.
user2864740

1
@ Suncat2000 L'introduction de plus de code comme des variables et des branches conditionnelles pour éviter d'utiliser un 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é.
atlaste

@atlaste: Désolé, permettez-moi de formuler mon commentaire plus clairement. Ce que j'aurais dû dire était: Belle explication. Mais les gens qui évitent goto ne sont pas des performances il s'agit d'éviter les abus qui provoquent un manque de lisibilité. Nous pouvons supposer que les personnes qui évitent goton'ont tout simplement pas la discipline de ne pas en abuser.
Suncat2000

@atlaste, quelle langue est "ackomplish"?
Pacerier

11

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.

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

10

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.


Entièrement d'accord. Introduire une nouvelle méthode juste pour se débarrasser d'un seul goto est idiot. Si le compilateur ne peut pas incorporer cet appel de méthode pour quelque raison que ce soit - nous nous retrouverons avec une surcharge complètement inutile d'un appel de méthode supplémentaire. Lancer et attraper des exceptions juste pour rompre la boucle est à la fois plus verbeux et ridiculement plus cher.
Zar Shardan

@Windowsprogramm: OP n'a pas demandé "pas d'utilisation de goto". Il ne voulait pas "passer à une autre méthode". La question était loin d'exclure toutes les voies possibles, mais vous avez raison de laisser entendre que goto est le meilleur ici.
Suncat2000

5

Parfois agréable d'abréger le code dans sa propre fonction et d'utiliser un retour anticipé - les premiers retours sont mauvais cependant:)

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

1
mais alors cela permet la solution traditionnelle demandée par OP
Surya Pratap

2

J'ai vu beaucoup d'exemples qui utilisent "break" mais aucun qui utilise "continue".

Il faudrait toujours un drapeau quelconque dans la boucle interne:

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

}

1

Depuis que j'ai vu breaken 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:

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.

3
Ensuite, un programmeur de maintenance insérera un autre niveau d'imbrication, corrigera certaines des instructions break et cassera certaines des autres instructions break. Le correctif consiste à passer à une étiquette à la place. Cela a vraiment été proposé, mais les pragmatiques utilisent plutôt goto a label.
Programmeur Windows

Attendez, qui fait la programmation de la maintenance? :)
Jesse C. Slicer

2
JavaScript a même étiqueté des blocs / instructions de rupture. devguru.com/Technologies/ecmascript/quickref/break.html
David Grant

@Chris Bartow: cool! fait mon Noël :) @David Grant: il semble donc que JS casse == C's goto?
Jesse C. Slicer

D a marqué la pause / continue
BCS

0

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:

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

... j'espère que cela aide ceux qui m'aiment sont des "fanboys" anti-goto :)


Désolé de vous le dire, mais votre conférencier est le seul à blâmer pour votre état. S'il avait pris la peine de vous forcer à apprendre l'assemblage, alors vous savez que «goto» n'est qu'un saut (bien sûr, j'ignore le fait qu'il s'agit d'une question ac #).
cmroanirgo

0

C'est comme ça que je l'ai fait. Encore une solution de contournement.

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


0

La façon la plus simple de terminer une double boucle serait de terminer directement la première boucle

string TestStr = "The frog jumped over the hill";
char[] KillChar = {'w', 'l'};

for(int i = 0; i < TestStr.Length; i++)
{
    for(int E = 0; E < KillChar.Length; E++)
    {
        if(KillChar[E] == TestStr[i])
        {
            i = TestStr.Length; //Ends First Loop
            break; //Ends Second Loop
        }
    }
}

Cela ne fonctionne pas car en utilisant break, vous terminerez la boucle interne. La boucle extérieure continuera à se répéter.
Alex Leo

0

Les boucles peuvent être rompues en utilisant des conditions personnalisées dans la boucle, ce qui permet d'avoir du code propre.

    static void Main(string[] args)
    {
        bool isBreak = false;
        for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
        {
            Console.WriteLine($"External loop iteration {i}");
            for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
            {
                Console.WriteLine($"Inner loop iteration {j}");

                // This code is only to produce the break.
                if (j > 3)
                {
                    isBreak = true;
                }                  
            }

            Console.WriteLine("The code after the inner loop will be executed when breaks");
        }

        Console.ReadKey();
    }

    private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;   

Avec ce code, nous avons la sortie suivante:

  • Itération de boucle externe 0
  • Itération de boucle interne 0
  • Itération de la boucle intérieure 1
  • Itération de la boucle intérieure 2
  • Itération de la boucle intérieure 3
  • Itération de la boucle intérieure 4
  • Le code après la boucle interne sera exécuté lors des pauses

0

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.

// self-invoked-anonymous-function
new Action(() =>
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits self invoked lambda expression
        }
    }
})();
Console.WriteLine("Hi");

-3

Lance une exception personnalisée qui sort de la boucle externe.

Cela fonctionne pour for, foreachou whileou tout type de boucle et tout langage qui utilise le try catch exceptionbloc

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

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

Quelle est la différence essentielle avec la réponse de dviljoen?
Gert Arnold

Cela ne fonctionnera pas, car vous ne vérifiez pas la condition "breakInnerLoop" dans la boucle for externe, vous passez donc simplement à la boucle suivante, vous avez également écrit j et J et manqué un point-virgule, qui ne sera pas compilé.
Sebastian

-4

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:

            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 et simple. :)


lisez la question avant de suggérer une pause. D'où les downvotes.
cmroanirgo

1
lisez-le maintenant, mais quand j'ai posté cette réponse, la version éditée de ne pas utiliser de booléen n'a pas été ajoutée c'est pourquoi j'ai posté cette réponse .. mais merci pour downvote!
Steve

1
Ce n'est pas une chose personnelle. Malheureusement, le vote est maintenant verrouillé;) C'est une partie nécessaire de SO pour obtenir les meilleures réponses au sommet (ce qui n'arrive pas toujours)
cmroanirgo

-6

Avez-vous même regardé le breakmot-clé? Oo

Ce n'est qu'un pseudo-code, mais vous devriez pouvoir voir ce que je veux dire:

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

Si vous pensez breakêtre une fonction comme break(), 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


13
Pas une question php.
Zerga

-10

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 ..!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.