Nombre variable de liens pour les boucles

Edit: Je suis désolé, mais j'ai oublié de mentionner que j'aurai besoin des valeurs des variables de compteur. Donc faire une boucle n'est pas une solution que j'ai peur.

Je ne sais pas si cela est possible, mais j'aimerais faire ce qui suit. Pour une fonction, un tableau de nombres est passé. Chaque numéro est la limite supérieure d'une boucle for, par exemple, si le tableau est [2, 3, 5] , le code suivant doit être exécuté:

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

Donc, la quantité d'anneaux pour les boucles est égale à la longueur du tableau. Y aurait-il moyen de faire fonctionner cela? Je pensais créer un code qui ajoute chacun pour une boucle à une chaîne, puis l'évalue via eval . J'ai lu cependant que eval ne devrait pas être le premier choix, car il peut aussi avoir des résultats dangereux.

Quelle technique pourrait être appropriée ici?

    La récursivité peut résoudre ce problème de façon soignée:

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

    Appelez-le comme ceci:

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

    La récurrence est une abjection ici. Une solution beaucoup plus rapide:

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

    Configurez un tableau de compteurs avec la même longueur que le tableau de limites. Utilisez une boucle unique et augmentez le dernier élément de chaque itération. Quand il atteint sa limite, vous le redémarrez et augmentez le prochain élément.

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

    Au lieu de penser en termes de nichées for boucles, pensez à des invocations de fonctions récursives. Pour faire votre itération, vous prenez la décision suivante (pseudo-code):

     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 

    Cela pourrait ressembler à ceci:

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

    Vous appelez la fonction avec un argument qui est votre liste de limites de compte et un argument que votre fonction doit exécuter pour chaque matrice de compte effectif (la partie "DoSomething"). La fonction principale ci-dessus fait tout le travail réel dans une fonction interne. Dans cette fonction interne, le premier argument est la liste des limites de compteur, qui sera "réduite" car la fonction s'appelle de manière récursive. Le deuxième argument sert à contenir l'ensemble actuel de valeurs de compteur, de sorte que "doSomething" puisse savoir que c'est sur une itération correspondant à une liste particulière de comptes réels.

    L'appel de la fonction ressemblerait à ceci:

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

    Il n'y a aucune différence entre faire trois boucles de 2, 3, 5 et une boucle 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(); }; } 

    Utilisation:

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

    Une solution qui fonctionne sans être compliquée par programmation serait de prendre les nombres entiers et de les multiplier par tous. Étant donné que vous n'êtes que la nidification de l'if, et que le plus interne possède des fonctionnalités, cela devrait fonctionner:

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

    Alternativement:

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

    Vous pouvez utiliser l'algorithme gourmand pour énumérer tous les éléments du produit cartésien 0: 2 x 0: 3 x 0: 5. Cet algorithme est effectué par ma fonction greedy_backward ci-dessous. Je ne suis pas un expert en Javascript et peut-être que cette fonction pourrait être améliorée.

     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 = ne*G[k]; } return epsilon; } 

    Il énumère les éléments du produit cartésien dans l'ordre anti-lexicographique (vous verrez l'énumération complète dans l'exemple 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 

    Il s'agit d'une généralisation de la représentation binaire (le cas lorsque les sizes=[2,2,2,...] ).

    Exemple:

     ~ 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"]) abc ~ 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 nécessaire, l'opération inverse est 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; } 

    Exemple :

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

    C'est ma tentative de simplifier la solution non récursive de Mike Samuel . J'ajoute également la possibilité de définir une plage (pas seulement le maximum) pour chaque argument entier.

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