-1

Here is the idea:

var a = [4, 5, 6];
for (var m = 0; m < a[0]; m++)
  for (var n = 0; n < a[1]; n++)
    for (var p = 0; p < a[2]; p++)
      console.log(`${m} + ${n} + ${p} = ${m+n+p}`);

Live Copy:

// This just tells the Stack Snippets in-snippet console not
// to throw away entries once it reaches a max (the default max
// is just the last 50 logs).
console.config({maxEntries: Infinity});

var a = [4, 5, 6];
for (var m = 0; m < a[0]; m++)
  for (var n = 0; n < a[1]; n++)
    for (var p = 0; p < a[2]; p++)
      console.log(`${m} + ${n} + ${p} = ${m+n+p}`);
/* This just makes the console take up the full output area */
.as-console-wrapper {
  max-height: 100% !important;
}

The code would get longer if the array a has more indexes. Could the code be shorten using Array.map or filter or a function?

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
uahnbu
  • 3
  • 6
  • Hi, do you want to sum all the values in the array? If yes, you could use [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). – Nora Aug 25 '18 at 15:44
  • 4
    Can you explain what you want to do? – michaelitoh Aug 25 '18 at 15:45
  • @Nora - The OP's code doesn't sum the values in the array. – T.J. Crowder Aug 25 '18 at 15:46
  • 1
    Sort of following on from @MichaelMontero's (good) question: Are you sure that output is the output you want? – T.J. Crowder Aug 25 '18 at 15:49
  • 1
    You'd need recursion to keep the code short. `map()` won't help, `filter()` even less. –  Aug 25 '18 at 15:53
  • Actually the output just show a part of the result. You can view the full output in the browser's console. – uahnbu Aug 25 '18 at 15:54
  • @uahnbu - I've fixed that, but the question remains: Is that the output you want? And what are you trying to do (Michael's question again). – T.J. Crowder Aug 25 '18 at 15:58
  • @PeterB - The CSS is there for a reason. No point in wasting most of the output area. *(Edit: Sorry, removed second part, that wasn't you...)* – T.J. Crowder Aug 25 '18 at 15:59
  • @T.J.Crowder Interesting, never knew it was possible to influence output like that. Still, odd to see it glued onto a question because it has no relation to the question itself. – Peter B Aug 25 '18 at 16:04
  • @T.J. Crowder Yeah actually I have to find all those sums and compare to a number, then return all set of m, n, p whose sum is smaller than that number. – uahnbu Aug 25 '18 at 16:06
  • @PeterB - Yeah, I hid the snippet and highlighted the actual problem code so the snippet-related parts don't jump out. – T.J. Crowder Aug 25 '18 at 16:06
  • So, you have to find numbers from 0 to arr[index] for each array entry and find sum in each iteration. Right? – Sagar V Aug 25 '18 at 16:08
  • That's what I need. – uahnbu Aug 25 '18 at 16:10

3 Answers3

1

It's easier if you break it down. First, you need to create a series per every element of your array.

let series = num => Array.from({ length: num + 1 }, (n, i) => i); //creates an array with nums from  0 to num.

That's the first part of your question. Then you need to do a cross product of your series.

Basically for two series [1, 2, 3] and [1, 2, 3, 4] you'll end up with a set of 12 elements:

[2, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 7]

And for that you could do:

let crossProduct = (a1, a2) => Array.prototype.concat.call(...a1.map(n1 => a2.map(n2 => n1 + n2)));

Now all you need to do is have a crossProduct for every series.

let final = numbers.map(series).reduce(crossProduct);

And there you have it:

let numbers = [4, 5, 6];
let series = num => Array.from({ length: num + 1 }, (n, i) => i); 
let crossProduct = (a1, a2) => Array.prototype.concat.call(...a1.map(n1 => a2.map(n2 => n1 + n2)));
let final = numbers.map(series).reduce(crossProduct);
console.log(final);

Edit: If it's from 0 to the number before (e.g. 4 is [0, 1, 2, 3]) then just take the + 1 in the series function.

2nd Edit: Less objects created for your crossProduct:

let crossProduct = (a1, a2) => {
    let resultingSet = [];
    for(let i = 0; i < a1.length; i++)
        for(let j = 0; j < a2.length; j++)
            resultingSet.push(a1[i] + a2[j]);
    return resultingSet;
} //only one array is created

And if you want to avoid having the series on memory all the time:

    let numbers = [4, 5, 6];
    let series = function* (num){
        for(let i = 0; i < num; i++){
            yield i;
        }
    }

    let crossProduct = (set, num) => {
       let resultingSet = [];
       for(let i = 0; i < set.length; i++){
           for(let j of series(num)){
               resultingSet.push(set[i] + j);
           }
       }
       return resultingSet;
    }

    let final = numbers.reduce(crossProduct, [0]);
    console.log(final);
MinusFour
  • 13,913
  • 3
  • 30
  • 39
  • There must be a way to avoid having all of those predictable sequences in memory. – T.J. Crowder Aug 25 '18 at 16:15
  • @T.J.Crowder they don't stay long on memory though, you might get into GC churn problems with big sets due to how I wrote `crossProduct` but you can rewrite that with a simple 2 for loop if you want to avoid an array per every element of your set. – MinusFour Aug 25 '18 at 16:30
  • Just seems like it shares the problem [this answer about cartesian products](https://stackoverflow.com/a/46951510/157247) does. But if you have enough memory to do it... :-) – T.J. Crowder Aug 25 '18 at 16:33
  • Oh you meant the series I generate – MinusFour Aug 25 '18 at 16:38
  • @T.J.Crowder yes, the 2 for loop will guarantee less objects created on the `crossProduct`. As for the series they can be made lazy with a generator but I don't know if performance would be better. It would depend on the javascript engine. – MinusFour Aug 25 '18 at 16:56
1

We can do this without taking up massive amounts of memory, and fairly simply, by using recursion:

const process = (array, n, numbers) => {
    if (n < array.length) {
        // Not done yet, recurse once for each number at this level
        const max = array[n];
        for (let i = 0; i < max; ++i) {
            process(array, n + 1, [...numbers, i]);
        }
    } else {
        // Done with this level, process the numbers we got
        console.log(`${numbers.join(" + ")} = ${numbers.reduce((s, e) => s + e)}`);
    }
}

process([4, 5, 6], 0, []);

Live Copy, with cross-checking against your results to ensure the above does the same thing:

// This just tells the Stack Snippets in-snippet console not
// to throw away entries once it reaches a max (the default max
// is just the last 50 logs).
console.config({maxEntries: Infinity});

function thisSolution() {
  const results = [];
  const process = (array, n, numbers) => {
      if (n < array.length) {
          // Not done yet, recurse once for each number at this level
          const max = array[n];
          for (let i = 0; i < max; ++i) {
              process(array, n + 1, [...numbers, i]);
          }
      } else {
          // Done with this level, process the numbers we got
          const result = numbers.reduce((s, e) => s + e);
          results.push(result);
          console.log(`${numbers.join(" + ")} = ${result}`);
      }
  }

  process([4, 5, 6], 0, []);
  return results;
}

function yourSolution() {
    const results = [];
    var a = [4, 5, 6];
    for (var m = 0; m < a[0]; m++)
      for (var n = 0; n < a[1]; n++)
        for (var p = 0; p < a[2]; p++)
          results.push(m + n + p);
    return results;
}

const thisResult = thisSolution();
const yourResult = yourSolution();
if (thisResult.some((entry, index) => entry !== yourResult[index])) {
    console.log("WRONG");
} else {
    console.log("RIGHT");
}
/* This just makes the console take up the full output area */
.as-console-wrapper {
  max-height: 100% !important;
}

This never goes deep into the stack (a.length + 1 stack frames, to be precise, so four in the example case). It builds up a number of temporary arrays (145 in the example case) that max out at a.length entries, releasing them as soon as they aren't needed anymore (a max of four are retained at any given time). Here's the quick and dirty metrics on that:

let maxStack = 0;
let stack = 0;
let totalArrays = 0;
let maxArrays = 0;
let arrays = 0;
// A wrapper for counting stack frames
const process = (...args) => {
    if (++stack > maxStack) {
        maxStack = stack;
    }
    const result = process2(...args);
    --stack;
    return result;
};
const process2 = (array, n, numbers) => {
    if (n < array.length) {
        // Not done yet, recurse once for each number at this level
        const max = array[n];
        for (let i = 0; i < max; ++i) {
            ++totalArrays;
            if (++arrays > maxArrays) {
                maxArrays = arrays;
            }
            process(array, n + 1, [...numbers, i]);
            --arrays;
        }
    } else {
        // Done with this level, process the numbers we got
        //console.log(`${numbers.join(" + ")} = ${numbers.reduce((s, e) => s + e)}`);
    }
}

process([4, 5, 6], 0, []);
++maxArrays;    // To account for the one in the last argument above
++totalArrays;  // "
console.log(`Max stack: ${maxStack}, max arrays: ${maxArrays}, total arrays: ${totalArrays}`);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Another solution that doesn't consume alot of memory and fairly efficient is by using an array that represnt the value of the indexes and update it each iteration. first you create an array that represent in each element the amount of iterations you need to run in order to update the indexes respectively for example for this array [1, 2, 3 ,4 ,5] you will get: [280, 140, 20, 5, 1] this means that index[0] will be updated each 280 iterations, index[1] will be updated each 140 iterations and so on.. totally you will run arr[n] * arr[n-1] * arr[n-2] * .... * arr[0] iterations as you did with ordinary nested for loop.

var arr = [1, 2, 7, 4, 5];

var indexes = Array.from({length: arr.length}, () => 0);
iterationsPerElement = arr.map((_, i) => arr.slice(i+1).reduce((acc, elem) => acc * elem, 1));

var totalIterations = iterationsPerElement[0] * arr[0];

for(var iteration = 1; iteration <= totalIterations; iteration++) {
    // sum those indexes
    console.log(`sum = ${indexes.reduce((acc, index) => acc + index, 0)}`);

    // update indexes
    for(i = 0; i < indexes.length; i++) {
        if(iteration % iterationsPerElement[i] == 0) {
            indexes[i]++;
            // empty the indexes on the right
            for(var j=i+1; j <indexes.length; j++) {
                indexes[j] = 0;
            }
        }
    }
}
Lihai
  • 323
  • 2
  • 6
  • 18