programacion iteraciones for flujo estructura ejemplos diagrama definicion ciclos ciclo bucles anidados anidado anidadas algoritmos javascript nested-loops combinatorics

javascript - iteraciones - Cantidad variable de anidados para bucles.



for anidado netbeans (8)

Edit: Lo siento, pero olvidé mencionar que necesitaré los valores de las variables de contador. Así que hacer un bucle no es una solución, me temo.

No estoy seguro de si esto es posible, pero me gustaría hacer lo siguiente. A una función, se le pasa una matriz de números. Cada número es el límite superior de un bucle for, por ejemplo, si la matriz es [2, 3, 5] , se debe ejecutar el siguiente código:

for(var a = 0; a < 2; a++) { for(var b = 0; b < 3; b++) { for(var c = 0; c < 5; c++) { doSomething([a, b, c]); } } }

Por lo tanto, la cantidad de bucles anidados es igual a la longitud de la matriz. ¿Habría alguna manera de hacer este trabajo? Estaba pensando en crear un fragmento de código que agregue cada bucle for a una cadena y luego lo evalúe a través de eval . Sin embargo, he leído que eval no debe ser la primera opción, ya que también puede tener resultados peligrosos.

¿Qué técnica podría ser apropiada aquí?


Configure una matriz de contadores con la misma longitud que la matriz límite. Use un solo bucle e incremente el último elemento en cada iteración. Cuando alcanza su límite, lo reinicia e incrementa el siguiente elemento.

function loop(limits) { var cnt = new Array(limits.length); for (var i = 0; i < cnt.length; i++) cnt[i] = 0; var pos; do { doSomething(cnt); pos = cnt.length - 1; cnt[pos]++; while (pos >= 0 && cnt[pos] >= limits[pos]) { cnt[pos] = 0; pos--; if (pos >= 0) cnt[pos]++; } } while (pos >= 0); }


En lugar de pensar en términos de anidados for bucles, piense en invocaciones de funciones recursivas. Para hacer su iteración, tomaría la siguiente decisión (pseudo código):

if the list of counters is empty then "doSomething()" else for (counter = 0 to first counter limit in the list) recurse with the tail of the list

Eso podría ser algo como esto:

function forEachCounter(counters, fn) { function impl(counters, curCount) { if (counters.length === 0) fn(curCount); else { var limit = counters[0]; curCount.push(0); for (var i = 0; i < limit; ++i) { curCount[curCount.length - 1] = i; impl(counters.slice(1), curCount); } curCount.length--; } } impl(counters, []); }

Llamaría a la función con un argumento que es su lista de límites de conteo, y un argumento que es su función para ejecutar para cada matriz de conteo efectiva (la parte "doSomething"). La función principal anterior hace todo el trabajo real en una función interna. En esa función interna, el primer argumento es la lista de límites de contador, que se "reducirá" a medida que la función se llama recursivamente. El segundo argumento se usa para mantener el conjunto actual de valores de contador, para que "doSomething" pueda saber que está en una iteración correspondiente a una lista particular de conteos reales.

Llamar a la función se vería así:

forEachCounter([4, 2, 5], function(c) { /* something */ });


Este es mi intento de simplificar la solución no recursiva de Mike Samuel . También agrego la capacidad de establecer un rango (no solo el máximo) para cada argumento entero.

function everyPermutation(args, fn) { var indices = args.map(a => a.min); for (var j = args.length; j >= 0;) { fn.apply(null, indices); // go through indices from right to left setting them to 0 for (j = args.length; j--;) { // until we find the last index not at max which we increment if (indices[j] < args[j].max) { ++indices[j]; break; } indices[j] = args[j].min; } } } everyPermutation([ {min:4, max:6}, {min:2, max:3}, {min:0, max:1} ], function(a, b, c) { console.log(a + '','' + b + '','' + c); });


La recursión es una exageración aquí. Una solución mucho más rápida:

function allPossibleCombinations(lengths, fn) { var n = lengths.length; var indices = []; for (var i = n; --i >= 0;) { if (lengths[i] === 0) { return; } if (lengths[i] !== (lengths[i] & 0x7ffffffff)) { throw new Error(); } indices[i] = 0; } while (true) { fn.apply(null, indices); // Increment indices. ++indices[n - 1]; for (var j = n; --j >= 0 && indices[j] === lengths[j];) { if (j === 0) { return; } indices[j] = 0; ++indices[j - 1]; } } } allPossibleCombinations([3, 2, 2], function(a, b, c) { console.log(a + '','' + b + '','' + c); })


La recursión puede resolver este problema perfectamente:

function callManyTimes(maxIndices, func) { doCallManyTimes(maxIndices, func, [], 0); } function doCallManyTimes(maxIndices, func, args, index) { if (maxIndices.length == 0) { func(args); } else { var rest = maxIndices.slice(1); for (args[index] = 0; args[index] < maxIndices[0]; ++args[index]) { doCallManyTimes(rest, func, args, index + 1); } } }

Llámalo así:

callManyTimes([2,3,5], doSomething);


No hay diferencia entre hacer tres bucles de 2, 3, 5 y un bucle de 30 (2 * 3 * 5).

function doLots (howMany, what) { var amount = 0; // Aggregate amount for (var i=0; i<howMany.length;i++) { amount *= howMany[i]; }; // Execute that many times. while(i--) { what(); }; }

Utilizar:

doLots([2,3,5], doSomething);


Puede utilizar el algoritmo codicioso para enumerar todos los elementos del producto cartesiano 0: 2 x 0: 3 x 0: 5. Este algoritmo es realizado por mi función greedy_backward abajo. No soy un experto en Javascript y tal vez esta función podría mejorarse.

function greedy_backward(sizes, n) { for (var G = [1], i = 0; i<sizes.length; i++) G[i+1] = G[i] * sizes[i]; if (n>=_.last(G)) throw new Error("n must be <" + _.last(G)); for (i = 0; i<sizes.length; i++) if (sizes[i]!=parseInt(sizes[i]) || sizes[i]<1){ throw new Error("sizes must be a vector of integers be >1"); }; for (var epsilon=[], i=0; i < sizes.length; i++) epsilon[i]=0; while(n > 0){ var k = _.findIndex(G, function(x){ return n < x; }) - 1; var e = (n/G[k])>>0; epsilon[k] = e; n = n-e*G[k]; } return epsilon; }

Enumera los elementos del producto cartesiano en el orden anti-lexicográfico (verá la enumeración completa en el ejemplo de doSomething ):

~ var sizes = [2, 3, 5]; ~ greedy_backward(sizes,0); 0,0,0 ~ greedy_backward(sizes,1); 1,0,0 ~ greedy_backward(sizes,2); 0,1,0 ~ greedy_backward(sizes,3); 1,1,0 ~ greedy_backward(sizes,4); 0,2,0 ~ greedy_backward(sizes,5); 1,2,0

Esta es una generalización de la representación binaria (el caso cuando sizes=[2,2,2,...] ).

Ejemplo:

~ function doSomething(v){ for (var message = v[0], i = 1; i<v.length; i++) message = message + ''-'' + v[i].toString(); console.log(message); } ~ doSomething(["a","b","c"]) a-b-c ~ for (var max = [1], i = 0; i<sizes.length; i++) max = max * sizes[i]; 30 ~ for(i=0; i<max; i++){ doSomething(greedy_backward(sizes,i)); } 0-0-0 1-0-0 0-1-0 1-1-0 0-2-0 1-2-0 0-0-1 1-0-1 0-1-1 1-1-1 0-2-1 1-2-1 0-0-2 1-0-2 0-1-2 1-1-2 0-2-2 1-2-2 0-0-3 1-0-3 0-1-3 1-1-3 0-2-3 1-2-3 0-0-4 1-0-4 0-1-4 1-1-4 0-2-4 1-2-4

Si es necesario, la operación inversa es simple:

function greedy_forward(sizes, epsilon) { if (sizes.length!=epsilon.length) throw new Error("sizes and epsilon must have the same length"); for (i = 0; i<sizes.length; i++) if (epsilon[i] <0 || epsilon[i] >= sizes[i]){ throw new Error("condition `0 <= epsilon[i] < sizes[i]` not fulfilled for all i"); }; for (var G = [1], i = 0; i<sizes.length-1; i++) G[i+1] = G[i] * sizes[i]; for (var n = 0, i = 0; i<sizes.length; i++) n += G[i] * epsilon[i]; return n; }

Ejemplo:

~ epsilon = greedy_backward(sizes, 29) 1,2,4 ~ greedy_forward(sizes, epsilon) 29


Una solución que funciona sin complicarse programáticamente sería tomar los números enteros y multiplicarlos a todos. Ya que solo está anidando los ifs, y solo el más interno tiene funcionalidad, esto debería funcionar:

var product = 0; for(var i = 0; i < array.length; i++){ product *= array[i]; } for(var i = 0; i < product; i++){ doSomething(); }

Alternativamente:

for(var i = 0; i < array.length; i++){ for(var j = 0; j < array[i]; j++){ doSomething(); } }