26

I have an array of arrays which looks like this:

changes = [ [1, 1, 1, -1], [1, -1, -1], [1, 1] ];

I want to get the next value in the array by adding the last value

values = [ [1, 2, 3, 2], [1, 0, -1], [1, 2] ];

so far I have tried to use a forEach:

changes.forEach(change => {
    let i = changes.indexOf(change);
    let newValue = change[i] + change[i + 1]
});

I think I am on the right lines but I cannot get this approach to work, or maybe there is a better way to do it.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
Team Cafe
  • 301
  • 1
  • 4
  • 6
  • 1
    Please elaborate this "I want to be able to increment the values based on the next value in the array to get this result:" – Syed Mehtab Hassan Mar 20 '19 at 10:51
  • I want to add the numbers in the array that are next to each other together to make the next number in the array – Team Cafe Mar 20 '19 at 10:52
  • 1
    This is just a [cumulative sum](https://stackoverflow.com/questions/20477177/creating-an-array-of-cumulative-sum-in-javascript) wrapped in `.map`. The [answer by Thomas](https://stackoverflow.com/a/55259153/7126740) is better than anything you'll find there though. – JollyJoker Mar 20 '19 at 12:21
  • 2
    @JollyJoker Thomas simulate `.reduce` method, why not use [.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) directly ? – R3tep Mar 20 '19 at 12:26
  • @R3tep You may technically be right, but I think Thomas' version ends up easier to read. – JollyJoker Mar 20 '19 at 12:28
  • @R3tep because I don't. Array#reduce accumulates/folds an array into a single value; Array#map maps one result-value for each item in the input array. I do both. I create one result for each item in the input while dragging along the accumulated sum. – Thomas Mar 20 '19 at 12:51
  • 1
    @Thomas Look my answer, you can use an array as accumulator. – R3tep Mar 20 '19 at 12:52
  • 1
    Never use `forEach` if you want to produce a result. – Bergi Mar 20 '19 at 13:55
  • I think this can be done with reduce. – fifn2 Mar 28 '19 at 14:55

8 Answers8

34

You could save a sum and add the values.

var array = [[1, 1, 1, -1], [1, -1, -1], [1, 1]],
    result = array.map(a => a.map((s => v => s += v)(0)));

console.log(result);

By using forEach, you need to take the object reference and the previous value or zero.

var array = [[1, 1, 1, -1], [1, -1, -1], [1, 1]];

array.forEach(a => a.forEach((v, i, a) => a[i] = (a[i - 1] || 0) + v));

console.log(array);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 1
    That first approach looks nice, but is **really** inefficient. – T.J. Crowder Mar 20 '19 at 10:58
  • 1
    @T.J.Crowder could you explain or point me in the right direction, why the first approach is inefficient? – Thomas Mar 20 '19 at 11:01
  • 1
    @Thomas - Look at all the functions it's creating and executing. [Your solution](https://stackoverflow.com/a/55259153/157247) is great -- as concise (*way* more so than mine) and still efficient. – T.J. Crowder Mar 20 '19 at 11:01
  • 2
    really, for three items in the outer array? if you really want to speed up the execution, you never use some array methods, instead use nested `for` loops and create new arrays. – Nina Scholz Mar 20 '19 at 11:05
  • @NinaScholz - We don't know what context it's meant to be used in. But hey, lots of different ways to approach a problem are good. – T.J. Crowder Mar 20 '19 at 11:06
  • each inner array is the y axis of a graph, for context. – Team Cafe Mar 20 '19 at 11:08
  • 1
    [This question](https://stackoverflow.com/questions/20477177/creating-an-array-of-cumulative-sum-in-javascript) could use an answer like the first one – JollyJoker Mar 20 '19 at 12:30
14

A version with map.

const changes = [
  [1, 1, 1, -1],
  [1, -1, -1],
  [1, 1]
];

const values = changes.map(array => {
  let acc = 0;
  return array.map(v => acc += v);
});

console.log(values);
.as-console-wrapper{top:0;max-height:100%!important}

And this doesn't change the source Array.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Thomas
  • 11,958
  • 1
  • 14
  • 23
  • Note that the array of arrays structure is just a needless complication. The question is actually about the cumulative sum of an array. I'd split out the outer mapping function into something called `cumulativeSum` – JollyJoker Mar 20 '19 at 12:25
4

New ESNext features of generators are nice for this.

Here I've created a simple sumpUp generator that you can re-use.

function* sumUp(a) {
  let sum = 0;
  for (const v of a) yield sum += v;
}

const changes = [ [1, 1, 1, -1], [1, -1, -1], [1, 1] ];
const values = changes.map(a => [...sumUp(a)]);
  
console.log(values);
Keith
  • 22,005
  • 2
  • 27
  • 44
3

You may use map function of Array

const changes = [ [1, 1, 1, -1], [1, -1, -1], [1, 1] ];    
const result = changes.map((v) => v.slice(0).map((t, i, arr) => i === 0 ? t : (arr[i] += arr[i - 1])))
console.log(changes);
console.log(result);

Update

Use slice to clone array. This will prevent changes to the original array.

Alex
  • 1,373
  • 8
  • 15
3

const changes = [ [1, 1, 1, -1], [1, -1, -1], [1, 1] ]
let values = []
changes.forEach(arr => {
  let accu = 0
  let nestedArr = []
  arr.forEach(n => {
    accu += n
    nestedArr.push(accu)
  })
  values.push(nestedArr)
})
console.log(values)
holydragon
  • 6,158
  • 6
  • 39
  • 62
2

Another way,

You can use .map to return your new array with the desired results. By using .reduce with an array as an accumulator, you can generate the subarray.

var array = [[1, 1, 1, -1], [1, -1, -1], [1, 1]],
    result = array.map(a => a.reduce((ac, v, i) => {
      const lastVal = ac[i-1] || 0;
      return [...ac, lastVal + v];
    }, []));

console.log(result);

// shorter
result = array.map(a => a.reduce((ac, v, i) => [...ac, (ac[i-1] || 0) + v], []));
console.log(result);
R3tep
  • 12,512
  • 10
  • 48
  • 75
2

Here is an easier to read way that iterates over the outer list of arrays. A copy of the inner array is made to keep the initial values (like [1, 1, 1, -1]). It then iterates over each value in the copied array and adds it to each index after it in the original array.

var changes = [[1, 1, 1, -1], [1, -1, -1], [1, 1]];
changes.forEach(subArray => {
    var subArrayCopy = subArray.slice();  // Create a copy of the current sub array (i.e. subArrayCopy = [1, 1, 1, -1];)
    subArrayCopy.forEach((val, index) => { // Iterate through each value in the copy
 for (var i = subArray.length - 1; i > index; i--) { // For each element from the end to the current index
            subArray[i] += val;  // Add the copy's current index value to the original array
 }
    });
})
console.log(changes);
Nick G
  • 1,953
  • 12
  • 16
1

You have an array of arrays, but each constituent array is independent from the next so let's tackle them individually. Let's talk about [1, 1, 1, -1].

Your use of the phrase "partial sum" is very informative. The full sum could be implemented using reduce:

[1, 1, 1, -1].reduce((x, y) => x + y);
// 2

But you want an array of partial sums. That's very similar to this usage of reduce, but instead of retaining only the last computed result, we retain every intermediate value too. In other languages, this is called scan (cf. F#, Haskell).

A javascript implementation of a generic scan would probably look a lot like reduce. In fact, you can implement it with reduce, with just a little extra work:

function scan(array, callback) {
  const results = [array[0]];

  // reduce does all the heavy lifting for us, but we define a wrapper to pass to it.
  array.reduce((...args) => {
    // The wrapper forwards all those arguments to the callback, but captures the result...
    const result = callback(...args);
    // ...storing that intermediate result in our results array...
    results.push(result);
    // ...then passes it back to reduce to continue what it was doing.
    return result;
  });

  return results;
}

// scan([1, 1, 1, -1], (x, y) => x + y) -> [1, 2, 3, 2]

A more robust implementation would hew closer to the standard library's reduce, especially around initial values:

function scan(array, callback, initialValue) {
  const results = [];

  const reducer = (...args) => {
    const result = callback(...args);
    results.push(result);
    return result;
  };

  if (arguments.length === 2) {
    results.push(array[0]);
    array.reduce(reducer);
  } else {
    results.push(initialValue);
    array.reduce(reducer, initialValue);
  }

  return results;
}

Bringing it back together, if you'd like to do this for your arrays of arrays, it would just be a map over scan:

[[1, 1, 1, -1], [1, -1, -1], [1, 1]].map(a => scan(a, (x, y) => x + y));
// [[1, 2, 3, 2], [1, 0, -1], [1, 2]]

No copying necessary or side effects to watch out for, and you get a handy higher-order function out of it to boot!

skelley
  • 475
  • 5
  • 15