可变数量的嵌套 for 循环

发布于 2024-10-11 21:15:00 字数 544 浏览 9 评论 0原文

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

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

什么技术可能适合这里?

Edit: I'm sorry, but I forgot to mention that I'll need the values of the counter variables. So making one loop isn't a solution I'm afraid.

I'm not sure if this is possible at all, but I would like to do the following.
To a function, an array of numbers is passed. Each number is the upper limit of a for loop, for example, if the array is [2, 3, 5], the following code should be executed:

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

So the amount of nested for loops is equal to the length of the array. Would there be any way to make this work? I was thinking of creating a piece of code which adds each for loop to a string, and then evaluates it through eval. I've read however that eval should not be one's first choice as it can have dangerous results too.

What technique might be appropriate here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

蓝海 2024-10-18 21:15:00

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

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

Recursion can solve this problem neatly:

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

Call it like this:

callManyTimes([2,3,5], doSomething);
橘虞初梦 2024-10-18 21:15:00

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

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)。如果我们没有足够的数字来进位,我们就完成了。

Recursion is overkill here. You can use generators:

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

The intuition here is that we keep a list of indices that are always less than the corresponding length.

The second loop handles carry. As when incrementing a decimal number 199, we go to (1, 9, 10), and then carry to get (1, 10, 0) and finally (2, 0, 0). If we don't have enough digits to carry into, we're done.

花开雨落又逢春i 2024-10-18 21:15:00

设置一个与限制数组长度相同的计数器数组。使用单个循环,并在每次迭代中递增最后一项。当它达到限制时,您可以重新启动它并增加下一个项目。

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

Set up an array of counters with the same length as the limit array. Use a single loop, and increment the last item in each iteration. When it reaches it's limit you restart it and increment the next item.

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);
}
在梵高的星空下 2024-10-18 21:15:00

一种在编程上不会变得复杂的解决方案是获取整数并将它们全部相乘。由于您只嵌套 if,并且只有最里面的 if 具有功能,因此这应该可行:

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

One solution that works without getting complicated programatically would be to take the integers and multiply them all. Since you're only nesting the ifs, and only the innermost one has functionality, this should work:

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

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

Alternatively:

for(var i = 0; i < array.length; i++){
    for(var j = 0; j < array[i]; j++){
        doSomething();
    }
}
若言繁花未落 2024-10-18 21:15:00

不要考虑嵌套的 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 */ });

Instead of thinking in terms of nested for loops, think about recursive function invocations. To do your iteration, you'd make the following decision (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

That might look something like this:

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

You'd call the function with an argument that's your list of count limits, and an argument that's your function to execute for each effective count array (the "doSomething" part). The main function above does all the real work in an inner function. In that inner function, the first argument is the counter limit list, which will be "whittled down" as the function is called recursively. The second argument is used to hold the current set of counter values, so that "doSomething" can know that it's on an iteration corresponding to a particular list of actual counts.

Calling the function would look like this:

forEachCounter([4, 2, 5], function(c) { /* something */ });
〆凄凉。 2024-10-18 21:15:00

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

This is my attempt at simplifying the non-recursive solution by Mike Samuel. I also add the ability to set a range (not just maximum) for every integer argument.

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

水溶 2024-10-18 21:15:00

进行 3 个 2、3、5 的循环和 1 个 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);

There's no difference between doing three loops of 2, 3, 5, and one loop of 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();
    };
}

Use:

doLots([2,3,5], doSomething);
空城缀染半城烟沙 2024-10-18 21:15:00

您可以使用贪心算法来枚举笛卡尔积 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

You can use the greedy algorithm to enumerate all elements of the cartesian product 0:2 x 0:3 x 0:5. This algorithm is performed by my function greedy_backward below. I am not an expert in Javascript and maybe this function could be improved.

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

It enumerates the elements of the Cartesian product in the anti-lexicographic order (you will see the full enumeration in the doSomething example):

~ 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

This is a generalization of the binary representation (the case when sizes=[2,2,2,...]).

Example:

~ 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

If needed, the reverse operation is 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;
}

Example :

~ epsilon = greedy_backward(sizes, 29)
1,2,4
~ greedy_forward(sizes, epsilon)
29
八巷 2024-10-18 21:15:00

还可以使用生成器来实现:

  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)) {
     //...
  }

One could also use a generator for that:

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

That can then be used as:

  for(const [j, k, l, m] of loop(1, 2, 3, 4)) {
     //...
  }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文