2011-01-13 18 views
18

編集:申し訳ありませんが、私はカウンタ変数の値が必要であることを忘れていました。ですから、1つのループを作ることは私が恐れる解決策ではありません。入れ子になったループの可変量

これはまったく可能かどうかわかりませんが、次のようにしたいと思います。 関数には、数値の配列が渡されます。各番号は、ループのための上限である配列が[2, 3, 5]ある場合、例えば、次のコードが実行されるべきである:

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

そこでループのネストされた量は、アレイの長さに等しいです。この仕事をする方法はありますか?私は、それぞれのループを文字列に追加し、evalで評価するコードを作成することを考えていました。私はしかし、evalが危険な結果をもたらす可能性があるので、1つ目の選択ではないはずです。

ここではどのような手法が適切でしょうか?

+2

だから、あなただけのいくつかの関数に渡された配列の数の積に等しい回数を呼びたいですか? – Sean

+0

いいえ、ごめんなさい。私はforループ(ここではa、b、c)の変数も必要になります。 – pimvdb

答えて

17

再帰はきちんとこの問題は解決することができます:

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

このように呼び出す:

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

あなたのソリューションは、魅力のようにも機能します。実際にあなたのものは一番清潔で、わかりやすいものです。ありがとうたくさん – pimvdb

+0

素晴らしいソリューションは、あなたも提案された最速のものです。Sean: '4X'、Guffa:' 8X'、Mike Samuel: '15X'、Pointy:' 28X'というベンチマークの 'X'としてネイティブネストループを使うと、私の(非科学的な)テストによると、 – serg

3

制限配列と同じ長さのカウンターの配列を設定します。単一のループを使用し、各反復で最後の項目をインクリメントします。それが限界に達すると、それを再起動して次の項目をインクリメントします。

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

2,3,5、および30のループ(3 * 3 * 5)の3つのループを実行することに違いはありません。

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

用途:

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

本当に申し訳ありませんが、カウンタ変数の値も必要になります。私はあなたのソリューションが大好きですが、その情報は失われてしまいます。 – pimvdb

+0

あなたは私にそれを打つ。また、どのような情報が必要ですか?あなたはあなたが持っている元の配列にすべての整数を保持できますか?それとも、彼らを別のやり方で開催させる必要がありますか? – user535617

+0

汎用の多次元配列関数を作成しようとしているので、インデックスの各組み合わせを値で埋める必要があります。したがって、forループをネストします。 1つのforループでインデックスが失われ、1つのインデックス(ここでは0〜30)が返されます – pimvdb

2

代わりに、ネストされたループforの観点で考えると、再帰関数呼び出しを考えます。あなたの反復を行うには、次の意思決定(擬似コード)を作ると思います:

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 

をこのような何かに見えるかもしれない:あなたはあなたのリストだ引数で関数を呼び出すと思います

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

を( "doSomething"の部分)を実行するための関数である引数を指定します。上記の主な機能は、内部関数のすべての実際の作業を行います。その内部関数では、最初の引数はカウンタ制限リストであり、関数が再帰的に呼び出されるときには "whittled down"されます。 2番目の引数は、現在のカウンタ値のセットを保持するために使用されるため、 "doSomething"は実際のカウントの特定のリストに対応する反復にあることを知ることができます。プログラム的に整数値を取り、それら全てを掛けることであろう複雑化せずに動作します

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

これは興味深いですね、ありがとう。 – pimvdb

1

一つの解決策:

関数を呼び出すと、このようになります。あなただけのIFSをネストしている、とだけ最も内側の1が機能を持っているので、これは動作するはずです:また

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

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

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

ここで再帰は過剰です。はるかに高速ソリューション:2×0:3×0:

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

それは巧妙な方法です。あなたは0x7fffffffでやっていることを説明することができますか? – pimvdb

+0

@pimvdbで、長さは負ではない整数であることが保証されているので、以下の 'index [j] === lengths [j]'チェックが通過する可能性があります。 –

+0

私は、これを凝縮させて、私のユースケースのためのより多用途にするいくつかの方法を見ました:https://stackoverflow.com/a/44753698/552067ありがとうマイク! –

0

あなたはデカルト積0のすべての要素を列挙する貪欲なアルゴリズムを使用することができます5。このアルゴリズムは下記の私の関数greedy_backwardによって実行されます。私はJavascriptの専門家ではなく、おそらくこの機能を改善することができます。

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

それは(あなたが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 

これは(バイナリ表現の一般化ですケースはsizes=[2,2,2,...])。

例:必要に応じて

~ 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 

、逆の操作は簡単です:

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

例:

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

これは、非再帰的solution by Mike Samuelを簡素化での私の試みです。また、整数引数ごとに範囲を設定する機能(最大値だけでなく)を追加しました。

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

関連する問題