27

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

我不确定这是否可能,但我想做以下事情。向函数传递一个数字数组。每个数字都是一个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不应该是一个人的首选,因为它也会产生危险的结果。

什么技术可能适合这里?

4

9 回答 9

23

递归可以巧妙地解决这个问题:

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);
于 2011-01-13T18:29:05.507 回答
10

递归在这里是多余的。您可以使用生成器:

function* allPossibleCombinations(lengths) {
  const n = lengths.length;

  let indices = [];
  for (let i = n; --i >= 0;) {
    if (lengths[i] === 0) { return; }
    if (lengths[i] !== (lengths[i] & 0x7fffffff)) { throw new Error(); }
    indices[i] = 0;
  }

  while (true) {
    yield indices;
    // Increment indices.
    ++indices[n - 1];
    for (let j = n; --j >= 0 && indices[j] === lengths[j];) {
      if (j === 0) { return; }
      indices[j] = 0;
      ++indices[j - 1];
    }
  }
}

for ([a, b, c] of allPossibleCombinations([3, 2, 2])) {
  console.log(`${a}, ${b}, ${c}`);
}

这里的直觉是我们保留一个总是小于相应长度的索引列表。

第二个循环手柄携带。就像在增加一个十进制数 199 时,我们转到 (1, 9, 10),然后进位得到 (1, 10, 0),最后是 (2, 0, 0)。如果我们没有足够的数字输入,我们就完成了。

于 2011-01-14T18:14:30.993 回答
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);
}
于 2011-01-13T18:14:25.717 回答
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”部分)执行的函数。上面的 main 函数在内部函数中完成所有实际工作。在那个内部函数中,第一个参数是计数器限制列表,当函数被递归调用时,它将被“缩减”。第二个参数用于保存当前的计数器值集,以便“doSomething”可以知道它在与特定的实际计数列表相对应的迭代中。

调用该函数将如下所示:

forEachCounter([4, 2, 5], function(c) { /* something */ });
于 2011-01-13T18:15:42.757 回答
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();
    }
}
于 2011-01-13T18:17:01.017 回答
1

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

于 2017-06-26T05:46:19.553 回答
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);
于 2011-01-13T18:14:25.887 回答
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
于 2015-04-07T10:01:01.027 回答
0

也可以为此使用生成器:

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

然后可以用作:

  for(const [j, k, l, m] of loop(1, 2, 3, 4)) {
     //...
  }
于 2019-06-05T20:33:58.350 回答