2011-01-13 15 views
18

Edytuj: Przykro mi, ale zapomniałem wspomnieć, że potrzebuję wartości zmiennych licznika. Więc obawianie jednej pętli nie jest rozwiązaniem.Zmienna ilość zagnieżdżonych pętli

Nie jestem pewien, czy jest to w ogóle możliwe, ale chciałbym wykonać następujące czynności. Do funkcji przekazywana jest tablica liczb. Każdy numer jest górna granica dla pętli, na przykład, jeśli tablica jest [2, 3, 5] następujące kodu powinny być wykonane:

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

Tak więc ilość zagnieżdżonej pętli jest równa długości tablicy. Czy byłby jakiś sposób, aby to zadziałało? Myślałem o stworzeniu kawałka kodu, który dodaje każdą pętlę for do łańcucha, a następnie ocenia go przez eval. Przeczytałem jednak, że eval nie powinien być pierwszym wyborem, ponieważ może również powodować niebezpieczne wyniki.

Jaka technika może być tutaj przydatna?

+2

Więc po prostu chcesz zadzwonić niektóre funkcje wielokrotnie która jest równa iloczynowi liczb w przekazywane w tablicy? – Sean

+0

Nie, przepraszam. Będę potrzebował również zmiennych pętli for (a, b i c). – pimvdb

Odpowiedz

17

Recursion może rozwiązać ten problem zgrabnie:

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

To tak:

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

Twoje rozwiązanie działa również jak urok. W rzeczywistości twoja jest najczystsza i łatwiejsza do zrozumienia dla mnie. Wielkie dzięki, – pimvdb

+0

Świetne rozwiązanie, twoje również jest najszybsze.Moje (nienaukowe) testy pokazują, że jeśli weźmiemy natywną pętlę zagnieżdżoną jako benchmark "X", wtedy: Sean: '4X', Guffa:' 8X', Mike Samuel: '15X', Pointy:' 28X'. – serg

3

Skonfiguruj tablicę liczników o tej samej długości, co tablica limitów. Użyj pojedynczej pętli i zwiększaj ostatni element w każdej iteracji. Po osiągnięciu limitu uruchom go ponownie i zwiększ następny przedmiot.

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

Nie ma różnicy między robieniem trzech pętli 2, 3, 5 i jednej pętli 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(); 
    }; 
} 

Zastosowanie:

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

Naprawdę przykro mi, ale będę potrzebował również wartości zmiennych licznika. Chociaż uwielbiam twoje rozwiązanie, informacje te giną. – pimvdb

+0

Uderzyłeś mnie do tego. Ponadto, jakiego rodzaju informacji potrzebujesz? Czy możesz po prostu zatrzymać wszystkie liczby całkowite w oryginalnej macierzy, którą masz? A może potrzebujesz ich w inny sposób? – user535617

+0

Próbuję utworzyć ogólną wielowymiarową funkcję tablicową, więc będę musiał wypełnić każdą kombinację indeksów wartością. Stąd zagnieżdżone pętle. Jedna pętla for powoduje utratę indeksów i zwraca jeden indeks (0-30 tutaj). – pimvdb

2

Zamiast myśleć w kategoriach zagnieżdżonych for pętli, pomyśl o rekurencyjnych wywołań funkcji. Aby wykonać iterację, można wprowadzić następujące decyzji (pseudo kod):

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 

To może wyglądać następująco:

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

Można by wywołać funkcję z argumentem, który jest lista limitów count i argument, który jest twoją funkcją do wykonania dla każdej efektywnej tablicy count (część "doSomething"). Główna funkcja powyżej wykonuje całą prawdziwą pracę w funkcji wewnętrznej. W tej funkcji wewnętrznej pierwszym argumentem jest lista ograniczeń, która zostanie "zmniejszona", ponieważ funkcja ta jest nazywana rekursywnie. Drugi argument służy do przechowywania bieżącego zestawu wartości liczników, aby "doSomething" mógł wiedzieć, że jest w iteracji odpowiadającej konkretnej liście rzeczywistych zliczeń.

Wywołanie funkcji będzie wyglądać następująco:

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

To brzmi interesująco, wielkie dzięki. – pimvdb

1

Jednym z rozwiązań, które działa bez uzyskiwania skomplikowane programowo byłoby wziąć liczby całkowite i pomnożyć je wszystkie.Skoro tylko gniazdowania IFS i tylko najgłębsza z nich ma funkcjonalność, to powinno działać:

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

for(var i = 0; i < product; i++){ 
    doSomething(); 
} 

Alternatywnie:

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

Rekursja jest tutaj przesadna. O wiele szybsze rozwiązanie:

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

To także genialny sposób. Czy mógłbyś wyjaśnić, co robisz z 0x7fffffff? – pimvdb

+0

@pimvdb, upewniając się, że długości są nieujemnymi liczbami całkowitymi, tak że 'indeksy [j] === długości [j]' sprawdź poniżej mają szansę na przekazanie. –

+0

Widziałem kilka sposobów na skondensowanie tego i uczynienie go bardziej wszechstronnym dla mojego przypadku użycia: https://stackoverflow.com/a/44753698/552067 Dzięki Mike! –

0

można użyć algorytmu chciwego wymienić wszystkie elementy iloczyn 0 2 x 0: 3 x 0: 5. Ten algorytm jest wykonywany przez moją funkcję greedy_backward poniżej. Nie jestem ekspertem od Javascript i być może ta funkcja mogłaby zostać ulepszona.

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

To wylicza elementy iloczynu kartezjańskiego w anty-porządku leksykograficznym (widać pełne wyliczenie w przykładzie 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 

Jest to uogólnienie reprezentacji binarnej (przypadek, gdy sizes=[2,2,2,...]).

przykład:

~ 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 

razie potrzeby, operacja odwrotna jest prosta

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

przykład:

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

to my próba uproszczenia nierekurencyjnych solution by Mike Samuel. Dodaję również możliwość ustawienia zakresu (nie tylko maksymalnego) dla każdego argumentu liczby całkowitej.

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