Переменное количество вложенных для циклов - PullRequest
23 голосов
/ 13 января 2011

Редактировать: извините, но я забыл упомянуть, что мне понадобятся значения переменных счетчика.Поэтому боюсь, что сделать один цикл - не решение проблемы.

Я не уверен, возможно ли это вообще, но я бы хотел сделать следующее.В функцию передается массив чисел.Каждое число является верхним пределом цикла 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 в строку, а затем оценивает его через eval.Однако я читал, что eval не должен быть первым выбором, поскольку он может иметь и опасные результаты.

Какая техника здесь может подойти?

Ответы [ 9 ]

19 голосов
/ 13 января 2011

Рекурсия может решить эту проблему аккуратно:

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);
9 голосов
/ 14 января 2011

Рекурсия здесь излишня.Гораздо более быстрое решение:

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); })
3 голосов
/ 13 января 2011

Настройка массива счетчиков той же длины, что и лимитный массив.Используйте один цикл и увеличивайте последний элемент в каждой итерации.Когда он достигает своего предела, вы перезапускаете его и увеличиваете следующий элемент.

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);
}
2 голосов
/ 13 января 2011

Одним из решений, которое работает без программных усложнений, было бы взять целые числа и умножить их все. Поскольку вы вкладываете только 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();
    }
}
2 голосов
/ 13 января 2011

Вместо того, чтобы думать в терминах вложенных циклов 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 */ });
1 голос
/ 26 июня 2017

Это моя попытка упростить нерекурсивное решение Майка Самуэля .Я также добавляю возможность устанавливать диапазон (не только максимальный) для каждого целочисленного аргумента.

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);
});
0 голосов
/ 05 июня 2019

Можно также использовать генератор для этого:

  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)) {
     //...
  }
0 голосов
/ 07 апреля 2015

Вы можете использовать жадный алгоритм для перечисления всех элементов декартового произведения 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
0 голосов
/ 13 января 2011

Нет разницы между выполнением трех циклов по 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);
...