¿Cuál es la mejor manera de romper los bucles anidados en JavaScript?

448

¿Cuál es la mejor manera de romper los bucles anidados 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
fuente
Aquí hay un buen ejemplo de ruptura de bucles y bloques de código: marcin-chwedczuk.github.io/…
csharpfolk

Respuestas:

1032

Al igual que 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
            }
        }
    }

como se define en EMCA-262 sección 12.12. [Documentos de MDN]

A diferencia de C, estas etiquetas solo se pueden usar continuey break, como Javascript no tiene goto.

efímero
fuente
387
WTF, ¿por qué no he visto esto en algún lugar de mis 3 años con JavaScript? / ..
Salman von Abbas
39
MDN dice "evitar el uso de etiquetas" por razones de legibilidad. ¿Por qué no es 'legible'? Porque nadie los usa, por supuesto. ¿Pero por qué no los usan? ...
XML
77
@Web_Designer Creo que tu comentario está desactualizado. En ninguna parte de los documentos de MDN dice "evitar el uso de etiquetas". Considera revisar o eliminar tu comentario.
Sean the Bean
8
@SeantheBean Hecho. Esta parece ser la respuesta más directa y no está abierta al abuso porque solo está disponible para continuey break.
Gary Willoughby
29
@ JérémyPouyet: su lógica para la votación negativa es absurda e injustificada. Responde a la pregunta del OP perfectamente. La pregunta no se refiere a sus opiniones con respecto a la legibilidad. Por favor, reconsidere su enfoque para ayudar a la comunidad.
The Dembinski
168

Envuelva eso en una función y luego simplemente return.

swilliams
fuente
12
Elijo aceptar esta respuesta porque es simple y se puede implementar de manera elegante. Absolutamente odio los GOTO y los considero una mala práctica ( puede abrir ), Ephemient está demasiado cerca de uno. ; o)
Gary Willoughby
16
En mi opinión, los GOTO están bien siempre que no rompan la estructuración. ¡Pero a cada uno lo suyo!
Ephemient
31
Las etiquetas para bucles no tienen absolutamente nada en común con GOTO, excepto su sintaxis. Simplemente se trata de romper con los bucles externos. No tienes ningún problema para romper el bucle más interno, ¿verdad? Entonces, ¿por qué tienes problemas para romper los bucles externos?
John Smith
11
Por favor considere aceptar la otra respuesta. Si no fuera por el comentario de Andrew Hedges (gracias por cierto), habría pensado: ah, entonces JavaScript no tiene esa característica. Y apuesto a que muchos en la comunidad podrían pasar por alto el comentario y pensar lo mismo.
John Smith
11
¿Por qué Stack Overflow no tiene una función que permita a la comunidad anular la respuesta seleccionada obviamente incorrecta? : /
Matt Huggins
85

Llego un poco tarde a la fiesta, pero el siguiente es un enfoque independiente del idioma que no utiliza GOTO / etiquetas o ajuste de funciones:

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

Por el lado positivo, fluye naturalmente, lo que debería complacer a la multitud que no es GOTO. En el lado negativo, el ciclo interno necesita completar la iteración actual antes de terminar, por lo que podría no ser aplicable en algunos escenarios.

aleemb
fuente
2
la llave de apertura no debe estar en nuevas líneas, porque las implementaciones de js pueden insertar dos puntos al final de la línea anterior.
Evgeny
21
@Evgeny: si bien algunas guías de estilo JavaScript requieren abrir llaves para ir en la misma línea, no es incorrecto tenerlo en una nueva línea y no hay peligro de que el intérprete inserte un punto y coma de forma ambigua. El comportamiento de ASI está bien definido y no se aplica aquí.
Jason Suárez
99
Solo asegúrate de comentar mucho sobre este enfoque. No es inmediatamente obvio lo que está sucediendo aquí.
Qix - MONICA FUE MALTRATADA el
1
Buena y simple respuesta. Esto debe considerarse como una respuesta, ya que no agota los bucles intensivos de la CPU (que es un problema con el uso de funciones) o no usa etiquetas, que generalmente no son legibles o no deberían usarse como dicen algunos. :)
Girish Sortur
2
Puede que me falte algo, pero para solucionar el problema del bucle interno que tiene que terminar esa iteración, ¿podría poner a breako continueinmediatamente después de establecer z e y? Me gusta la idea de usar las forcondiciones del bucle para comenzar. Elegante a su manera.
Ben Sutton
76

Me doy cuenta de que este es un tema muy antiguo, pero como mi enfoque estándar aún no está aquí, pensé en publicarlo para los futuros googlers.

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
fuente
2
Si se conditionevalúa trueen la primera iteración del bucle anidado, aún ejecuta el resto de las 10 iteraciones, verificando el abortvalor cada vez. Este no es un problema de rendimiento para 10 iteraciones, pero sería con, digamos, 10,000.
Robusto
77
No, está saliendo de ambos bucles. Aquí está el violín de demostración . No importa qué condición establezcas, sale después de que se cumpla.
zord
44
La optimización sería agregar un descanso; después de configurar abort = true; y eliminando la comprobación de condición de aborto del bucle final.
xer21
1
Me gusta esto, pero creo que, en un sentido general, harías muchos procesamientos innecesarios, es decir, para cada iteración de cada iterador aborty la expresión. En escenarios simples, eso podría estar bien, pero para bucles enormes con
miles de millones de
1
¿Están realmente discutiendo si verificar un solo valor booleano 10000 veces es rápido o lento? intente 100 millones de veces / suspiro
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
}

¿Como es que? :)

harley.333
fuente
2
Supuse que esto era a lo que se refería
swilliams
18
Esto agrega un costo de tiempo de ejecución significativo si el ciclo es grande: el intérprete / compilador Javascript (o "compreter" en estos días, una combinación de ambos) debe crear un nuevo contexto de ejecución para la función (y en algún momento liberado por GC). CADA VEZ.
Mörre
2
En realidad, esto es bastante peligroso porque pueden suceder algunas cosas extrañas que puede no estar esperando. En particular, debido al cierre creado con var x, si alguna lógica dentro del ciclo hace referencia a x en un momento posterior (por ejemplo, define una función anónima interna que se guarda y ejecuta más tarde), el valor de x será lo que sea estaba al final del ciclo, no el índice durante el cual se definió la función. (continuación)
devios1
1
Para evitar esto, debe pasar xcomo parámetro a su función anónima para que cree una nueva copia de la misma, que luego puede ser referenciada como un cierre ya que no cambiará a partir de ese momento. En resumen, recomiendo la respuesta de ephemient.
devios1
2
Además, creo que la legibilidad es una mierda total. Esto es mucho más vago que una etiqueta. Las etiquetas solo se ven como ilegibles porque nadie las usa.
Qix - MONICA FUE MALTRATADA el
39

Bastante sencillo:

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
fuente
Estoy de acuerdo en que esta es realmente la mejor, la función uno no escala, ajustando todos los bucles si tampoco escala, es decir, hace que sea difícil de leer y depurar ... esta es increíble. Puede declarar vars loop1, loop2, loop3 y agregar una pequeña declaración al final. También para romper múltiples bucles, necesitaría hacer algo comoloop1=loop2=false;
Muhammad Umer,
22

Aquí hay cinco formas de salir de los bucles anidados en JavaScript:

1) Establecer el ciclo de los padres al final

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

2) Usar etiqueta

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

3) Usar 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) Utilice la función de ejecución automática

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

5) Use la función regular

function nested_loops()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
}
nested_loops();
Dan Bray
fuente
1
@Wyck ¡No puedo estar lo suficientemente de acuerdo! Es una pena que JavaScript no solo tenga una sintaxis break 2;como la que tenemos en PHP. Sin etiquetas de bucle, sin funciones, sin verificaciones if-else, sin moderación con / voladura de variables de bucle, ¡simplemente sintaxis limpia!
Jay Dadhania
1
Ejemplo 4 es ingenioso
leroyjenkinss24
14

¿Qué tal si no se usan interrupciones, no hay marcas de cancelación y no se realizan comprobaciones adicionales de condición? Esta versión solo dispara las variables de bucle (las hace Number.MAX_VALUE) cuando se cumple la condición y obliga a todos los bucles a terminar con elegancia.

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

Hubo una respuesta similar para los bucles anidados de tipo decreciente, pero esto funciona para bucles anidados de tipo incremental sin necesidad de considerar el valor de terminación de cada bucle para bucles simples.

Otro ejemplo:

// 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
fuente
4

¿Qué tal llevar los bucles a sus límites finales?

    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;
            }
         }
       }
     }
usuario889030
fuente
1
Creo que la respuesta de Drakes tiene la misma lógica de una manera más sucinta y clara.
Ingeniero Toast
¡Absolutamente brillante!
geoyws
3

Si usa Coffeescript, existe una conveniente palabra clave "do" que facilita la definición y ejecución inmediata de una función anónima:

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

... así que simplemente puede usar "retorno" para salir de los bucles.

Nick Perkins
fuente
Esto no es lo mismo. Mi ejemplo original tiene tres forbucles, no dos.
Gary Willoughby
2

Pensé en mostrar un enfoque de programación funcional. Puede salir de las funciones anidadas Array.prototype.some () y / o Array.prototype.every (), como en mis soluciones. Un beneficio adicional de este enfoque es que Object.keys()enumera solo las propiedades enumerables de un objeto, mientras que "un bucle for-in enumera también las propiedades en la cadena del prototipo" .

Cerca de la solución del 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;
    }

Solución que reduce la iteración sobre los encabezados / elementos:

    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
fuente
2

Ya mencionado anteriormente por swilliams , pero con un ejemplo a continuación (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
fuente
0

Hmmm hola a la fiesta de 10 años?

¿Por qué no poner alguna condición en tu para?

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

Así te detienes cuando quieres

En mi caso, usando Typecript, podemos usar algunos () que pasan por la matriz y se detienen cuando se cumple la condición. Entonces mi código se vuelve así:

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

De esta manera, el ciclo se detuvo justo después de que se cumpla la condición

Recordatorio: este código se ejecuta en TypeScript

Azutanguy
fuente
-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
fuente
99
Esto parece más confuso que el original.
Cristiano Fontes
21
Como un poema posmoderno
Digerkam
Se votó porque un do while se está convirtiendo más en este tipo de escenario (en la mayoría de los casos).
Cody
-4

la mejor manera es:
1) Ordenar los dos conjuntos que se usan en el primer y segundo bucle.
2) si el elemento coincide, rompa el bucle interno y mantenga el valor del índice.
3) cuando comience la próxima iteración, inicie el bucle interno con el valor de índice de retención.

Deepak Karma
fuente