2

Given a multi-dimensional array:

var a = [[3,2,5], [4,1,7], [1,6,8]];

I would like to do a cumulative sum of each array to return the following result:

[[3,2,5], [7,3,12], [8,9,20]];
  • cum sum on 1st elements of each sub-array: 3 4 1
  • cum sum on 2nd elements of each sub-array: 2 1 6
  • cum sum on 3rd elements of each sub-array: 5 7 8

I've tried using reduce(), but can't quite get the expected result.

Any suggestions, much appreciated.

S

UPDATE - Taking it to the next level:

var a = [
    [new Date(), 3,2,5], 
    [new Date(), null,1,7], 
    [new Date(), null,6,8], 
    [new Date(), 1,2,3]
];

Should result in:

[[new Date(), 3,2,5], 
 [new Date(), null,3,12],
 [new Date(), null,9,20],
 [new Date(), 4,11,23]]

My approach was to create a multi-dimensional offsetIndex array:

var offsetIdx = [];
        for (var i=1; i<a.length; i++) {

            for (var z=0; z<a[i].length; z++) {
                var zValue = a[i][z];

                oIdx = offsetIdx[z] || 0;

                a[i][z] = zValue && z!==0 ? a[i-1-oIdx][z] + zValue : zValue;

                if(!zValue){
                    offsetIdx[z] = oIdx + 1;
                } else {
                    offsetIdx[z] = 0; 
                }
            }
        }

Happy to see other approaches and ways of making it ultra light-weight.

Seb
  • 89
  • 1
  • 8
  • How is it that your output array can have the same number of elements, given that a sum will be applied to the input? – New Alexandria Mar 24 '16 at 02:47
  • @NewAlexandria: He's accumulating the result as he goes along into each array, so the first array doesn't change, the second is the sum of the first plus itself (for each member respectively), the third is the sum of the result of the second, again plus its own respective members, and so on. –  Mar 24 '16 at 02:50
  • Correct. I leaned towards the reduce() function as it passes a reference to the previous element. I've seen examples with flat 1 dim arrays, but can't quite adapt it to accommodate a multi-dim array. – Seb Mar 24 '16 at 02:54
  • It would help if you post your reduce() code. It should work and I'd like to see what you're doing wrong (or I'm doing wrong) if it's not. – djechlin Mar 24 '16 at 03:10

5 Answers5

3
for (var i=1; i<a.length; i++) {
  for (var z=0; z<a[i].length; z++) {
   a[i][z] = a[i-1]][z] + a[i][z]
  }
}

The array should be updated dynamically as the loop runs on. This is destructive so it will modify the original array.

char
  • 91
  • 5
  • Thanks guys, all viable solutions. I like @char approach as it's light-weight, uses vanilla JS and ultra-fast. Nice one. – Seb Mar 24 '16 at 03:00
  • Thanks for the comment Seb. I got told off by my employer when I opted to use libraries or non loop methods as they make things slower. – char Mar 24 '16 at 03:19
  • @char educate your employer on how more maintainable code and fewer bugs is more important than unmeasured speed gains :) – djechlin Apr 13 '16 at 15:34
3

  function cumulativeSum(arr) {
    var result = [arr[0]];
    for(var i = 1; i < arr.length; i++) {
        result.push([]);
        for(var j = 0; j < arr[0].length; j++) {
          result[i].push(result[i - 1][j] + arr[i][j]);
        }
    }
    return result;
}
    
document.body.innerHTML = JSON.stringify(cumulativeSum(
  [[3,2,5], [4,1,7], [1,6,8]]
))

Unlike the other answer, this one is not destructive, preserving the original array and returning the result.

afuous
  • 1,478
  • 10
  • 12
  • This one won't work as the previous result will not be updated. For example when you reach the 3rd array, the 2nd array has not been updated yet to include the first arrays sum, so you will only be getting 2nd array + 3rd array values instead of 1st + 2nd + 3rd array values. – char Mar 24 '16 at 02:59
  • 1
    @char: It does work because he's taking the previous values from the result, not the original. –  Mar 24 '16 at 03:02
  • 1
    @char https://jsfiddle.net/hojwxane/ It does work. The reason is that the result array does store the original the cumulative sum and the next element is set to be the sum of the previous element of the result array and the next element of the original array. – afuous Mar 24 '16 at 03:03
  • Woops my bad didn't see he was using the results value. – char Mar 24 '16 at 03:06
1

Using Array.reduce, it would look like this

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

var arr2 = arr.reduce(function(a,b) {
    var nested = Array.isArray(a[0]);
    b = b.map(function(x,i) {
     return x + (nested ? a[a.length-1] : a)[i];
    });
    if ( nested ) a.push(b);
    return nested ? a : [a,b];
});

document.body.innerHTML = '<pre>' + JSON.stringify(arr2, 0, 4) + '</pre>';

Here's a sligthly "optimized" (golfed) version, passing in a starting point for the reduction and slicing the array

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

var arr2 = arr.slice(1).reduce(function(a,b) {
 return [a.push(b.map(function(x,i) {return x+a[a.length-1][i]})), a].pop();
},[arr[0]]);

document.body.innerHTML = '<pre>' + JSON.stringify(arr2, 0, 4) + '</pre>';

Making it a one-liner by using ES2015

var arr  = [[3,2,5], [4,1,7], [1,6,8]];
var a2 = arr.slice(1).reduce((a,b)=>[a,a.push(b.map((x,i)=>x+a[a.length-1][i]))][0],[arr[0]]);

document.body.innerHTML = '<pre>' + JSON.stringify(a2, 0, 4) + '</pre>';
adeneo
  • 312,895
  • 29
  • 395
  • 388
0

This will give you the sum across all elements. Not quite what you ask, but I will leave this answer here for future visitors who catch the question title.

  1. Flatten first using your favorite library (underscore and lodash both have it)
  2. Then reduce + sum .

    _.flatten([1, [2, [3, [4]], 5]]);
    // → [1, 2, [3, [4]], 5]
    
djechlin
  • 59,258
  • 35
  • 162
  • 290
0

What a mathy question.

Why not transpose the array first? An answer on this question - Transposing a 2D-array in JavaScript - suggests the underscore.js solution:

_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])

to do this. There are many ways.

Then the sum should be much easier - just .map(f) where f is your sum on one array function.

IMO this is a good and readable solution, because "transpose+sum" is very true to the column-sum nature of the question, and I would avoid an imperative or loop-heavy solution that obscures this.

Community
  • 1
  • 1
djechlin
  • 59,258
  • 35
  • 162
  • 290