Пример :

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

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

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

 Sean13 янв. 2011 г., 19:13
Итак, вы просто хотите вызвать некоторую функцию несколько раз, которая равна произведению чисел в переданном массиве?
 Peter Krauss03 февр. 2019 г., 20:53
Смотрите более общую проблему с более простыми, современными и элегантными решениями наэтот вопрос / решения
 pimvdb13 янв. 2011 г., 19:19
Нет, извините. Мне также понадобятся переменные цикла for (a, b и c).

Ответы на вопрос(8)

Решение Вопроса

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);
 Reath30 мая 2018 г., 21:10
@ Шон, твоя рекурсия работает безупречно, спасибо тебе за это. Последние 2 дня я безуспешно пытался перекодировать его, чтобы можно было начинать с заданных индексов (args в твоей функции). Например, я хотел быcallManyTimes([5,5,5], doSomething); не начиная с[0,0,0], но начиная с[2, 4, 5], Может ли кто-нибудь помочь мне добиться этого с помощью кода @Sean?
 pimvdb13 янв. 2011 г., 19:35
Ваше решение также работает как шарм. На самом деле ваш самый чистый и легче для меня понять. большое спасибо
 serg23 мар. 2013 г., 23:15
Отличное решение, ваше тоже самое быстрое из предложенных. Мое (ненаучное) тестирование показывает, что если мы возьмем собственный вложенный цикл в качестве эталонаXтогда: Шон:4XГуффа:8XМайк Самуэль:15XЗаостренный:28X.
 Sean03 июн. 2018 г., 22:08
@ Reath Добавить довольно простоminIndices параметр:pastebin.com/rxswG7bj

декартового произведения 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

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

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

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

 pimvdb15 янв. 2011 г., 11:30
Это тоже гениальный способ. Не могли бы вы объяснить, что вы делаете с 0x7fffffff?
 Mike Samuel15 янв. 2011 г., 18:55
@pimvdb, гарантируя, что длины являются неотрицательными целыми числами, так чтоindices[j] === lengths[j] проверка ниже имеет шанс пройти.
 Web_Designer26 июн. 2017 г., 07:48
Я видел несколько способов сжать это и сделать его более универсальным для моего случая использования:stackoverflow.com/a/44753698/552067 Спасибо, Майк!

которое работает без усложнения программно, состоит в том, чтобы взять целые числа и умножить их все. Так как вы вкладываете только if, и только самый внутренний из них имеет функциональность, это должно работать:

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

for(var i = 0; i < product; i++){
    doSometh,ing();
}

В качестве альтернативы:

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

чтобы думать с точки зрения вложенного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 */ });
 pimvdb13 янв. 2011 г., 19:17
Это звучит интересно, большое спасибо.

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);
 user53561713 янв. 2011 г., 19:19
Ты подтолкнул меня на это. Кроме того, какая информация вам нужна? Можете ли вы просто хранить все целые числа в исходном массиве, который у вас есть? Или вам нужно держать их по-другому?
 pimvdb13 янв. 2011 г., 19:16
Мне очень жаль, но мне также понадобятся значения переменных счетчика. Хотя мне нравится ваше решение, эта информация теряется.
 pimvdb13 янв. 2011 г., 19:22
Я пытаюсь создать универсальную функцию многомерного массива, поэтому мне нужно заполнить каждую комбинацию индексов значением. Отсюда и вложенные циклы. Один цикл for приводит к потере индексов и просто возвращает один индекс (0 - 30 здесь)

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

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

Ваш ответ на вопрос