2011-01-13 15 views
18

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

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

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

Así la cantidad de anidado para los bucles es igual a la longitud de la matriz. ¿Hubiera alguna forma de hacer que esto funcione? Estaba pensando en crear un fragmento de código que agregue cada uno de los bucles a una cadena, y luego lo evalúa a través del eval. Sin embargo, he leído que eval no debería ser la primera opción, ya que también puede tener resultados peligrosos.

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

+2

Por lo que sólo quieren llamar a alguna función de un número de veces que es igual al producto de los números en una matriz pasada-in? – Sean

+0

No, lo siento. Necesitaré también las variables de los bucles for (a, byc aquí). – pimvdb

Respuesta

17

recursividad puede resolver este problema claramente:

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); 
+0

Su solución también funciona como un encanto. En realidad, el tuyo es el más limpio y más fácil de entender para mí. Muchas gracias – pimvdb

+0

Gran solución, la suya también es la más rápida propuesta.Mi prueba (no científica) muestra que si tomamos el ciclo anidado nativo como un punto de referencia 'X', entonces: Sean:' 4X', Guffa: '8X', Mike Samuel:' 15X', Puntiagudo: '28X'. – serg

3

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

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

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

Uso:

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

Lo siento mucho, pero también voy a necesitar los valores de las variables de contador. Aunque me encanta su solución, esa información se pierde. – pimvdb

+0

Me ganaste. Además, ¿para qué tipo de información lo necesitas? ¿Puedes simplemente mantener todos los enteros en la matriz original que tienes? ¿O necesitas tenerlos de alguna manera diferente? – user535617

+0

Estoy tratando de crear una función de matriz multidimensional genérica, así que tendré que completar cada combinación de índices con un valor. De ahí los bucles for anidados. One for loop hace que los índices se pierdan y solo devuelve un índice (0 - 30 aquí) – pimvdb

2

En lugar de pensar en términos de for bucles anidados, pensar en invocaciones de funciones recursivas. Para hacer su iteración, que serías la decisión siguiente (pseudocó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, []); 
} 

Se podría llamar a la función con un argumento que es su lista de los límites de recuento, y un argumento que es su función para ejecutar para cada matriz de recuento 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ímite de contador, que se "reducirá gradualmente" a medida que la función se llame recursivamente. El segundo argumento se utiliza para mantener el conjunto actual de valores de contador, de modo que "doSomething" pueda saber que está en una iteración correspondiente a una lista particular de recuentos reales.

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

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

Eso suena interesante, muchas gracias. – pimvdb

1

Una solución que funciona sin ser complicado programáticamente sería tomar los números enteros y se multiplica a todos.Dado que sólo está anidando las FI, y sólo la más interna 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(); 
} 

alternativa:

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

La recursividad es exagerada aquí. Una solución mucho más rápido:

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

+0

Esa es una manera ingeniosa, también. ¿Podrías explicar lo que estás haciendo con 0x7fffffff? – pimvdb

+0

@pimvdb, asegurándose de que las longitudes sean números enteros no negativos para que la verificación 'indices [j] === lengths [j]' a continuación tenga una probabilidad de pasar. –

+0

Vi algunas formas de condensar esto y hacerlo más versátil para mi caso de uso: https://stackoverflow.com/a/44753698/552067 ¡Gracias, Mike! –

0

Usted puede utilizar el algoritmo voraz 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 a continuación. 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 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 

esto 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 
0

Este es mi intento de simplificar la no-recursivo solution by 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); 
 
});

Cuestiones relacionadas