Quelle est la meilleure façon de rompre avec les boucles imbriquées en JavaScript?

449

Quelle est la meilleure façon de rompre avec les boucles imbriquées en Javascript?

//Write the links to the page.
for (var x = 0; x < Args.length; x++)
{
   for (var Heading in Navigation.Headings)
   {
      for (var Item in Navigation.Headings[Heading])
      {
         if (Args[x] == Navigation.Headings[Heading][Item].Name)
         {
            document.write("<a href=\"" 
               + Navigation.Headings[Heading][Item].URL + "\">" 
               + Navigation.Headings[Heading][Item].Name + "</a> : ");
            break; // <---HERE, I need to break out of two loops.
         }
      }
   }
}
Gary Willoughby
la source
Voici un bon exemple de rupture de boucles et de blocs de code: marcin-chwedczuk.github.io/…
csharpfolk

Réponses:

1034

Tout comme Perl,

loop1:
    for (var i in set1) {
loop2:
        for (var j in set2) {
loop3:
            for (var k in set3) {
                break loop2;  // breaks out of loop3 and loop2
            }
        }
    }

comme défini dans EMCA-262 section 12.12. [MDN Docs]

Contrairement à C, ces étiquettes ne peuvent être utilisées que pour continueet break, comme Javascript ne l'est pas goto.

éphémère
la source
388
WTF pourquoi n'ai-je pas vu cela être utilisé quelque part dans mes 3 années avec JavaScript: / ..
Salman von Abbas
40
MDN dit "éviter d'utiliser des étiquettes" uniquement pour des raisons de lisibilité. Pourquoi n'est-il pas «lisible»? Parce que personne ne les utilise, bien sûr. Mais pourquoi ne les utilisent-ils pas? ...
XML
7
@Web_Designer Je pense que votre commentaire est obsolète. Nulle part dans les documents MDN il n'est dit "d'éviter d'utiliser des étiquettes". Veuillez envisager de réviser ou de supprimer votre commentaire.
Sean the Bean
8
@SeantheBean Done. Cela semble être la réponse la plus simple et non susceptible d'abus, car elle n'est disponible que pour continueet break.
Gary Willoughby
29
@ JérémyPouyet - Votre logique de vote négatif est stupide et injustifiée. Il répond parfaitement à la question du PO. La question ne concerne pas vos opinions concernant la lisibilité. Veuillez reconsidérer votre approche pour aider la communauté.
The Dembinski
168

Enveloppez cela dans une fonction, puis juste return.

swilliams
la source
12
J'ai choisi d'accepter cette réponse car elle est simple et peut être mise en œuvre de manière élégante. Je déteste absolument les GOTO et les considère comme une mauvaise pratique ( peut ouvrir ), celle d'Ephemient est trop proche. ; o)
Gary Willoughby
16
IMO, les GOTO vont bien tant qu'ils ne cassent pas la structuration. Mais à chacun ses goûts!
ephémient
31
Les étiquettes des boucles n'ont absolument rien de commun avec GOTO, à l'exception de leur syntaxe. Il s'agit simplement de rompre avec les boucles extérieures. Vous n'avez aucun problème à rompre la boucle la plus intérieure, n'est-ce pas? alors pourquoi avez-vous un problème avec la rupture des boucles externes?
John Smith
11
Veuillez envisager d'accepter l'autre réponse. Sinon pour le commentaire d'Andrew Hedges (merci btw.), J'aurais pensé: ah, donc javascript n'a pas cette fonctionnalité. Et je parie que beaucoup dans la communauté pourraient ignorer le commentaire et penser de la même façon.
John Smith
11
Pourquoi Stack Overflow ne dispose-t-il pas d'une fonctionnalité permettant à la communauté de remplacer la réponse sélectionnée manifestement erronée? : /
Matt Huggins
85

Je suis un peu en retard à la fête, mais ce qui suit est une approche indépendante du langage qui n'utilise pas GOTO / labels ou wrapping de fonction:

for (var x = Set1.length; x > 0; x--)
{
   for (var y = Set2.length; y > 0; y--)
   {
      for (var z = Set3.length; z > 0; z--)
      {
          z = y = -1; // terminates second loop
          // z = y = x = -1; // terminate first loop
      }
   }
}

À la hausse, il coule naturellement, ce qui devrait plaire à la foule non-GOTO. Sur le plan négatif, la boucle interne doit terminer l'itération actuelle avant de se terminer, de sorte qu'elle pourrait ne pas être applicable dans certains scénarios.

aleemb
la source
2
l'accolade ouvrante ne doit pas être sur de nouvelles lignes, car les implémentations js peuvent insérer un deux-points à la fin de la ligne précédente.
Evgeny
21
@Evgeny: alors que certains guides de style JavaScript appellent l'ouverture d'accolades pour aller sur la même ligne, il n'est pas incorrect de l'avoir sur une nouvelle ligne et il n'y a aucun danger que l'interprète insère de manière ambiguë un point-virgule. Le comportement d'ASI est bien défini et ne s'applique pas ici.
Jason Suárez
9
Assurez-vous simplement de commenter l'enfer de cette approche. Ce n'est pas immédiatement évident ce qui se passe ici.
Qix - MONICA A ÉTÉ BRUÉE
1
Réponse agréable et simple. Cela devrait être considéré comme une réponse, car cela ne fatigue pas les boucles gourmandes en CPU (ce qui est un problème avec l'utilisation des fonctions) ou n'utilise pas d'étiquettes, qui ne sont généralement pas lisibles ou ne doivent pas être utilisées comme certains le disent. :)
Girish Sortur
2
Il me manque peut-être quelque chose, mais pour contourner le problème de la boucle intérieure devant terminer cette itération, pourriez-vous mettre un breakou continueimmédiatement après avoir défini z et y? J'aime l'idée d'utiliser les forconditions de la boucle pour démarrer. Élégant à sa manière.
Ben Sutton
76

Je me rends compte que c'est un sujet vraiment ancien, mais comme mon approche standard n'est pas encore là, j'ai pensé le poster pour les futurs googleurs.

var a, b, abort = false;
for (a = 0; a < 10 && !abort; a++) {
    for (b = 0; b < 10 && !abort; b++) {
        if (condition) {
            doSomeThing();
            abort = true;
        }
    }
}
zord
la source
2
Si l' conditionévalue à truela première itération de la boucle imbriquée, vous exécutez toujours les 10 autres itérations, en vérifiant la abortvaleur à chaque fois. Ce n'est pas un problème de performances pour 10 itérations, mais ce serait avec, disons, 10 000.
Robusto
7
Non, il sort des deux boucles. Voici le violon de démonstration . Quelle que soit la condition que vous définissez, elle se ferme une fois remplie.
zord
4
L'optimisation serait d'ajouter une pause; après avoir défini abort = true; et en supprimant! abort condition check de la boucle finale.
xer21
1
J'aime cela, mais je pense que dans un sens général, vous feriez beaucoup de traitements inutiles - c'est-à-dire pour chaque itération de chaque évaluateur itéré abortet l'expression. Dans des scénarios simples, cela pourrait être bien, mais pour d'énormes boucles avec des itérations de gazillions qui pourraient être un problème
Z. Khullah
1
Êtes-vous vraiment en train de vous demander si la vérification d'une valeur booléenne unique 10000 fois est rapide ou lente? essayez 100 millions de fois / soupir
fabspro
40
var str = "";
for (var x = 0; x < 3; x++) {
    (function() {  // here's an anonymous function
        for (var y = 0; y < 3; y++) {
            for (var z = 0; z < 3; z++) {
                // you have access to 'x' because of closures
                str += "x=" + x + "  y=" + y + "  z=" + z + "<br />";
                if (x == z && z == 2) {
                    return;
                }
            }
        }
    })();  // here, you execute your anonymous function
}

Comment ça? :)

harley.333
la source
2
Je pensais que c'était ce à quoi swilliams voulait en venir
harley.333
18
Cela ajoute un coût d'exécution important si la boucle est volumineuse - un nouveau contexte d'exécution pour la fonction doit être créé (et à un moment donné libéré par GC) par l'interpréteur / compilateur Javascript (ou, "compreter" ces jours-ci, un mélange des deux) A CHAQUE FOIS.
Mörre
2
C'est en fait assez dangereux car des choses étranges peuvent se produire auxquelles vous ne vous attendez pas. En particulier, en raison de la fermeture créée avec var x, si une logique dans la boucle fait référence à x à un moment ultérieur (par exemple, elle définit une fonction anonyme interne qui est enregistrée et exécutée plus tard), la valeur de x sera ce qu'elle était à la fin de la boucle, pas l'index pendant lequel la fonction a été définie. (suite)
devios1
1
Pour contourner ce problème, vous devez passer xen paramètre à votre fonction anonyme afin qu'elle en crée une nouvelle copie, qui peut ensuite être référencée comme une fermeture car elle ne changera pas à partir de ce moment. En bref, je recommande la réponse de l'éphémient.
devios1
2
De plus, je pense que la lisibilité est de la merde complète. C'est beaucoup plus vague qu'une étiquette. Les étiquettes sont uniquement considérées comme illisibles car personne ne les utilise jamais.
Qix - MONICA A ÉTÉ BRUÉE
39

Assez facile:

var a = [1, 2, 3];
var b = [4, 5, 6];
var breakCheck1 = false;

for (var i in a) {
    for (var j in b) {
        breakCheck1 = true;
        break;
    }
    if (breakCheck1) break;
}
akinuri
la source
Je suis d'accord pour dire que c'est en fait la meilleure, la fonction n'est pas mise à l'échelle, enveloppant tout pour les boucles si elle n'est pas mise à l'échelle, c'est-à-dire qu'elle est difficile à lire et à déboguer .... celle-ci est géniale. Vous pouvez simplement déclarer vars loop1, loop2, loop3 et ajouter une petite instruction à la fin. Pour rompre plusieurs boucles, vous devez également faire quelque chose commeloop1=loop2=false;
Muhammad Umer
22

Voici cinq façons de sortir des boucles imbriquées en JavaScript:

1) Définissez la boucle parent (s) à la fin

for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            i = 5;
            break;
        }
    }
}

2) Utilisez une étiquette

exit_loops:
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
            break exit_loops;
    }
}

3) Utiliser une variable

var exit_loops = false;
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            exit_loops = true;
            break;
        }
    }
    if (exit_loops)
        break;
}

4) Utiliser la fonction auto-exécutable

(function()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
})();

5) Utilisez la fonction régulière

function nested_loops()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
}
nested_loops();
Dan Bray
la source
1
@Wyck Je ne suis pas assez d'accord! C'est dommage que javascript n'ait pas simplement une syntaxe break 2;comme nous l'avons en PHP. Pas d'étiquettes de boucle, pas de fonctions, pas de vérifications if-else, pas de tempérage avec / dynamitage des variables de boucle - juste une syntaxe propre!
Jay Dadhania
1
L'exemple 4 est astucieux
leroyjenkinss24
14

Que diriez-vous de n'utiliser aucune interruption, aucun indicateur d'abandon et aucun contrôle de condition supplémentaire. Cette version blastes juste les variables de boucle (les fait Number.MAX_VALUE) lorsque la condition est remplie et force toutes les boucles à se terminer avec élégance.

// No breaks needed
for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 10; j++) {
    if (condition) {
      console.log("condition met");
      i = j = Number.MAX_VALUE; // Blast the loop variables
    }
  }
}

Il y avait une réponse similaire pour les boucles imbriquées de type décrémentation, mais cela fonctionne pour les boucles imbriquées de type incrémentation sans avoir à prendre en compte la valeur de terminaison de chaque boucle pour les boucles simples.

Un autre exemple:

// No breaks needed
for (var i = 0; i < 89; i++) {
  for (var j = 0; j < 1002; j++) {
    for (var k = 0; k < 16; k++) {
      for (var l = 0; l < 2382; l++) {
        if (condition) {
          console.log("condition met");
          i = j = k = l = Number.MAX_VALUE; // Blast the loop variables
        }
      }
    }
  }
}
Drakes
la source
4

Que diriez-vous de pousser les boucles à leurs limites

    for(var a=0; a<data_a.length; a++){
       for(var b=0; b<data_b.length; b++){
           for(var c=0; c<data_c.length; c++){
              for(var d=0; d<data_d.length; d++){
                 a =  data_a.length;
                 b =  data_b.length;
                 c =  data_b.length;
                 d =  data_d.length;
            }
         }
       }
     }
user889030
la source
1
Je pense que la réponse de Drakes a la même logique d'une manière plus succincte et plus claire.
Ingénieur Toast
absolument brillant!
geoyws
3

Si vous utilisez Coffeescript, il existe un mot clé "do" pratique qui facilite la définition et l'exécution immédiate d'une fonction anonyme:

do ->
  for a in first_loop
    for b in second_loop
      if condition(...)
        return

... vous pouvez donc simplement utiliser "retour" pour sortir des boucles.

Nick Perkins
la source
Ce n'est pas pareil. Mon exemple original a trois forboucles et non deux.
Gary Willoughby
2

Je pensais que je montrerais une approche de programmation fonctionnelle. Vous pouvez sortir des fonctions imbriquées Array.prototype.some () et / ou Array.prototype.every (), comme dans mes solutions. Un avantage supplémentaire de cette approche est qu'elle Object.keys()énumère uniquement les propriétés énumérables d'un objet, tandis que "une boucle for-in énumère également les propriétés de la chaîne de prototypes" .

Proche de la solution OP:

    Args.forEach(function (arg) {
        // This guard is not necessary,
        // since writing an empty string to document would not change it.
        if (!getAnchorTag(arg))
            return;

        document.write(getAnchorTag(arg));
    });

    function getAnchorTag (name) {
        var res = '';

        Object.keys(Navigation.Headings).some(function (Heading) {
            return Object.keys(Navigation.Headings[Heading]).some(function (Item) {
                if (name == Navigation.Headings[Heading][Item].Name) {
                    res = ("<a href=\""
                                 + Navigation.Headings[Heading][Item].URL + "\">"
                                 + Navigation.Headings[Heading][Item].Name + "</a> : ");
                    return true;
                }
            });
        });

        return res;
    }

Solution qui réduit l'itération sur les en-têtes / articles:

    var remainingArgs = Args.slice(0);

    Object.keys(Navigation.Headings).some(function (Heading) {
        return Object.keys(Navigation.Headings[Heading]).some(function (Item) {
            var i = remainingArgs.indexOf(Navigation.Headings[Heading][Item].Name);

            if (i === -1)
                return;

            document.write("<a href=\""
                                         + Navigation.Headings[Heading][Item].URL + "\">"
                                         + Navigation.Headings[Heading][Item].Name + "</a> : ");
            remainingArgs.splice(i, 1);

            if (remainingArgs.length === 0)
                return true;
            }
        });
    });
Zachary Ryan Smith
la source
2

Déjà mentionné précédemment par swilliams , mais avec un exemple ci-dessous (Javascript):

// Function wrapping inner for loop
function CriteriaMatch(record, criteria) {
  for (var k in criteria) {
    if (!(k in record))
      return false;

    if (record[k] != criteria[k])
      return false;
  }

  return true;
}

// Outer for loop implementing continue if inner for loop returns false
var result = [];

for (var i = 0; i < _table.length; i++) {
  var r = _table[i];

  if (!CriteriaMatch(r[i], criteria))
    continue;

  result.add(r);
}
Matt Borja
la source
0

Hmmm salut à la fête des 10 ans?

Pourquoi ne pas mettre une condition dans votre?

var condition = true
for (var i = 0 ; i < Args.length && condition ; i++) {
    for (var j = 0 ; j < Args[i].length && condition ; j++) {
        if (Args[i].obj[j] == "[condition]") {
            condition = false
        }
    }
}

Comme ça tu t'arrêtes quand tu veux

Dans mon cas, en utilisant Typescript, nous pouvons utiliser certains () qui passent par le tableau et s'arrêtent lorsque la condition est remplie Donc mon code devient comme ceci:

Args.some((listObj) => {
    return listObj.some((obj) => {
        return !(obj == "[condition]")
    })
})

Comme ça, la boucle s'est arrêtée juste après que la condition soit remplie

Rappel: ce code s'exécute en TypeScript

Azutanguy
la source
-3
XXX.Validation = function() {
    var ok = false;
loop:
    do {
        for (...) {
            while (...) {
                if (...) {
                    break loop; // Exist the outermost do-while loop
                }
                if (...) {
                    continue; // skips current iteration in the while loop
                }
            }
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        if (...) {
            break loop;
        }
        ok = true;
        break;
    } while(true);
    CleanupAndCallbackBeforeReturning(ok);
    return ok;
};
Triqui
la source
9
Cela semble plus déroutant que l'original.
Cristiano Fontes
21
Comme un poème postmoderne
Digerkam
A voté parce qu'un do while est de plus en plus adapté à ce type de scénario (dans la plupart des cas).
Cody
-4

la meilleure façon est -
1) Trier les deux tableaux qui sont utilisés dans la première et la deuxième boucle.
2) si l'élément correspond, rompez la boucle intérieure et maintenez la valeur d'index.
3) au début de l'itération suivante, lancer la boucle interne avec une valeur d'index de maintien.

Deepak Karma
la source