可变数量的嵌套for循环

时间:2011-01-13 18:09:32

标签: javascript nested-loops combinatorics

编辑:对不起,但我忘了提到我需要计数器变量的值。所以制作一个循环不是我害怕的解决方案。

我不确定这是否可行,但我想做以下事情。 对于函数,传递数字数组。每个数字都是for循环的上限,例如,如果数组是[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]);
          }
     }
}

因此嵌套for循环的数量等于数组的长度。有没有办法让这项工作?我正在考虑创建一段代码,将每个for循环添加到字符串中,然后通过eval对其进行评估。不过,我已经读到eval不应该是首选,因为它也会产生危险的结果。

这里适合哪种技术?

9 个答案:

答案 0 :(得分:19)

递归可以很好地解决这个问题:

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

答案 1 :(得分:9)

递归在这里是过度的。更快的解决方案:

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

答案 2 :(得分: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);
}

答案 3 :(得分: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”部分)。上面的主要功能完成了内部函数的所有实际工作。在该内部函数中,第一个参数是计数器限制列表,当函数被递归调用时,它将被“缩小”。第二个参数用于保存当前的计数器值集,以便“doSomething”可以知道它在与特定实际计数列表相对应的迭代中。

调用该函数将如下所示:

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

答案 4 :(得分:2)

一种解决方案,无需以编程方式进行复杂操作,即采用整数并将它们全部乘以。由于您只是嵌套ifs,并且只有最里面的ifs具有功能,因此这应该有效:

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

答案 5 :(得分:1)

这是我尝试简化非递归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);
});

答案 6 :(得分:0)

做三个循环的2,3,5和一个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();
    };
}

使用:

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

答案 7 :(得分:0)

您可以使用贪婪算法枚举笛卡尔积0:2 x 0:3 x 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

答案 8 :(得分:0)

也可以为此使用生成器:

app.intent('Location', (conv, {'geo-city': geoCity}) => {
  const luckyNumber = geoCity.length;
  // ...
});

然后可以用作:

  function loop(...times) {
    function* looper(times, prev = []) {
       if(!times.length) {
           yield prev;
           return;
       }
       const [max, ...rest] = times;
       for(let current = 0; current < max; current++) {
         yield* looper(rest, [...prev, current]);
       }
   }
   return looper(times);
 }