Подтвердить что ты не робот

Переменная количество вложенных циклов

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

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

Таким образом, количество вложенных циклов равно длине массива. Будет ли какой-нибудь способ сделать эту работу? Я думал создать кусок кода, который добавляет каждый цикл в строку, а затем оценивает его через eval. Однако я прочитал, что eval не должен быть одним из первых, поскольку он может также иметь опасные результаты.

Какой метод может быть уместен здесь?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 4

Вместо того, чтобы думать в терминах вложенных циклов 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, []);
}

Вы вызываете функцию с аргументом, который содержит список лимитов счета, и аргумент, который ваша функция должна выполнять для каждого эффективного массива count (часть "doSomething" ). Основная функция выше делает всю реальную работу во внутренней функции. В этой внутренней функции первым аргументом является список счетчиков пределов, который будет "сокращаться", поскольку функция называется рекурсивно. Второй аргумент используется для хранения текущего набора значений счетчика, так что "doSomething" может знать, что он на итерации, соответствующей определенному списку фактических счетчиков.

Вызов функции будет выглядеть так:

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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