Shonna
Shonna

Reputation: 1143

Creating an array of cumulative sum in javascript

This is an example of what I need to do:

var myarray = [5, 10, 3, 2];

var result1 = myarray[0];
var result2 = myarray[1] + myarray[0];
var result3 = myarray[2] + myarray[1] + myarray[0];
var result4 = myarray[3] + myarray[2] + myarray[1] + myarray[0];

so all that would output 5, 15, 18, 20

but instead of writing out all the vars like that, I want it to say something like:

var result = arrayitem + the sum of any previous items 

Does that make sense? Is that possible? How do I do that?

Upvotes: 62

Views: 66191

Answers (30)

TopMarx
TopMarx

Reputation: 135

Adapting Matt's answer to ES6, this is the most readable to me:

const array   = [5, 10, 3, 2];

const cumpts  = [];
array.reduce((a, b, i) => cumpts[i] = a + b, 0);

console.log(cumpts);

Upvotes: 0

Nycki
Nycki

Reputation: 970

Could you do this in a one-liner? Sure.

const values = [5, 10, 4, 2];
const sums = values.reduce((a, x, i) => [...a, x + (i && a[i-1])], []);

Should you? Probably not. This is more readable:

function cumulativeSums(values) {
    let total = 0;
    const sums = [];
    values.forEach(v => {
        total += v;
        sums.push(total);
    });
    return sums;
}

const values = [5, 10, 4, 2];
const sums = cumulativeSums(values);

Upvotes: 1

agnivesh
agnivesh

Reputation: 115

This is what I found to be the most intuitive:

arr.reduce((a, x) => [...a, a[a.length - 1] + x], [0]).slice(1)

To get around zero index issues (in the other answers), just add a zero at the start, and strip it off later.

Upvotes: 1

Ateeb Asif
Ateeb Asif

Reputation: 184

here's a simpler solution, using array.reduce that keeps track of all the sum and push it in array to make a new runningSum Array input: [5, 10, 3, 2] output: [ 5, 15, 18, 20 ]

const arr = [5, 10, 3, 2];

const sumArr = [];
arr.reduce((previousValue, currentValue) => {
     const sum = previousValue + currentValue;
     sumArr.push(sum);
     return sum;
    }, 0);

  console.log("sumArr ", sumArr);

Upvotes: 0

Rahimuddin
Rahimuddin

Reputation: 614

For someone who is looking for array of objects,

Sample array of objects

  {
    "month": "2020-05",
    "start_date": "2020-05-01",
    "end_date": "2020-05-31",
    "invoices_count": 19,
    "total_margin": 44420.83,
    "total_revenue": 415826.48999999993
  },
  {
    "month": "2020-10",
    "start_date": "2020-10-01",
    "end_date": "2020-10-31",
    "invoices_count": 25,
    "total_margin": 62583.130000000005,
    "total_revenue": 553906.81
  },
  {
    "month": "2020-09",
    "start_date": "2020-09-01",
    "end_date": "2020-09-30",
    "invoices_count": 21,
    "total_margin": 46459.35,
    "total_revenue": 397549.66000000003
  }]

The below is the code to get cumulative sum of total_margin for each object

data.map((item, index) =>
    index === 0
      ? item
      : {
          ...data[index],
          'total_margin': data
            .slice(0, index + 1)
            .reduce((prev, curr) => prev + curr[total_margin], 0),
        }
  );

Expected output:

[
    {
        "month": "2020-05",
        "start_date": "2020-05-01",
        "end_date": "2020-05-31",
        "invoices_count": 19,
        "total_margin": 44420.83,
        "total_revenue": 415826.48999999993
    },
    {
        "month": "2020-10",
        "start_date": "2020-10-01",
        "end_date": "2020-10-31",
        "invoices_count": 25,
        "total_margin": 107003.96,
        "total_revenue": 553906.81
    },
    {
        "month": "2020-09",
        "start_date": "2020-09-01",
        "end_date": "2020-09-30",
        "invoices_count": 21,
        "total_margin": 153463.31,
        "total_revenue": 397549.66000000003
    }]

Upvotes: 0

Nick
Nick

Reputation: 147286

You could do this with reduce, initialising with an array with [0] in it and then appending the sum of the iterated and previous accumulator value in each loop, the cutting off the first value (with slice) at the end:

const arr = [2, 1, 4, 3, 2]

const res = arr.reduce((acc, v, i) => acc.concat(acc[i] + v), [0]).slice(1)

console.log(res)

Upvotes: 0

Shayan Shaikh
Shayan Shaikh

Reputation: 65

Here is the simplest answer using a reducer.

let runningSum = function(nums) {
    nums.reduce((pre, curr, i) => {
        nums[i] = pre + curr;
        return nums[i];
    }, 0);
    return nums;
};

console.log(runningSum([1,2,3,5,6,8]));

Runtime: 133 ms, faster than 5.02% of JavaScript online submissions for Running Sum of 1d Array. Memory Usage: 41.9 MB, less than 93.93% of JavaScript online submissions for Running Sum of 1d Array.

Upvotes: 0

JollyJoker
JollyJoker

Reputation: 1406

An elegant solution copied from Nina Scholz, using currying to access the previous value.

const cumulativeSum = (sum => value => sum += value)(0);

console.log([5, 10, 3, 2].map(cumulativeSum));

cumulativeSum is the function value => sum += value, with sum initialized to zero. Every time it's called, sum is updated and will equal the previous value (output[n-1]) when called the next time (with input[n]).

Note that sum will need to be set to zero explicitly when you want to reuse the summation. The most convenient way to handle this may be to just inline the code instead of using a named function.

console.log([5, 10, 3, 2].map((sum => value => sum += value)(0)));
console.log([6, 10, 3, 2].map((sum => value => sum += value)(0)));

vs the unwanted behavior

const cumulativeSum = (sum => value => sum += value)(0);

console.log([5, 10, 3, 2].map(cumulativeSum));
console.log([6, 10, 3, 2].map(cumulativeSum));

Update 7.6.2022: You can use the comma operator instead of currying to make an easier-to-read version with explicit sum = 0.

console.log([5, 10, 3, 2].map((sum = 0, n => sum += n)));

Three years and no one thought to suggest this? :)

Upvotes: 95

SnazzyPencil
SnazzyPencil

Reputation: 79

Here's a simple answer using recursion.

function sumArrayRec(arr) {
  return sumArrayHelper(0, 0, [], arr)
}

function sumArrayHelper(prevSum, index, sums, arr) {
  if (!arr.length) {
    return sums
  }

  let curSum = arr[index] + prevSum
  sums.push(curSum)
  array.shift()

  return sumArrayHelper(curSum, index++, sums, array)
}

Upvotes: 0

Flynn
Flynn

Reputation: 351

I came up with this ES6 one using array.map()

function prefixSum(nums) {
  let psum = 0;
  return nums.map(x => psum += x);
};

console.log(prefixSum([5, 10, 20, 30]));

Upvotes: 4

user1555785
user1555785

Reputation: 41

var nums= [5, 10, 3, 2];
var runningSum = function(nums) {
   
    nums.reduce((acc, _, i) => (nums[i] += acc));
    return nums;
};

Upvotes: 1

Don-Pierre Halfaway
Don-Pierre Halfaway

Reputation: 505

/*Checkout the explanation below */

nums = [1,2,3,4]
var runningSum = function(nums) { 
shoppingCart =[];
runningtotal =0;  
nums.forEach(EachValue => {  
runningtotal += EachValue
shoppingCart.push(runningtotal);  
});
return shoppingCart       
};

console.log(runningSum(nums));

/* define some numbers*/

nums = [1,2,3,4]

/* assign function runningSum , some numbers*/

var runningSum = function(nums) { 
shoppingCart =[]; /* Create a empty shopping cart to store the items */
runningtotal =0;  /* Start with your beginning bill of zero items in the cart */

/* remove a number from list of numbers call each one of the numbers from the array => EachValue using a pointer function*/

nums.forEach(EachValue => {  

         (runningtotal += EachValue) 

/* Now add the value to the runningtotal to represent items prices*/

shoppingCart.push(runningtotal);  

/* Use the push method to place it into the new array called shopping cart */ });

return shoppingCart

/* output the items currently in the shopping cart with only 1d prices */

};

    nums = [1,2,3,4]
    var runningSum = function(nums) { 
    shoppingCart =[];
    runningtotal =0;  
    nums.forEach(EachValue => {  
    runningtotal += EachValue
    shoppingCart.push(runningtotal);  
    });
    return shoppingCart       
    };

    console.log(runningSum(nums));

Upvotes: 0

Roy Ashbrook
Roy Ashbrook

Reputation: 854

I needed to keep the results and just add a running total property. I had a json object with a date and revenue and wanted to display a running total as well.

//i'm calculating a running total of revenue, here's some sample data
let a = [
  {"date":  "\/Date(1604552400000)\/","revenue":  100000 },
  {"date":  "\/Date(1604203200000)\/","revenue":  200000 },
  {"date":  "\/Date(1604466000000)\/","revenue":  125000 },
  {"date":  "\/Date(1604293200000)\/","revenue":  400000 },
  {"date":  "\/Date(1604379600000)\/","revenue":  150000 }
];

//outside accumulator to hold the running total
let c = 0;

//new obj to hold results with running total
let b = a
  .map( x => ({...x,"rtotal":c+=x.revenue}) )
  
//show results, use console.table if in a browser console
console.log(b)

Upvotes: 6

fnb
fnb

Reputation: 11

Old school and simpler :

let myarray = [5, 10, 3, 2], result = [];

for (let i = 0, s = myarray[0]; i < myarray.length; i++, s += myarray[i]) result.push(s);

console.log(result); // [5, 15, 18, 20]

Upvotes: 1

Rahat Dhande
Rahat Dhande

Reputation: 53

This question has been answered well by others but I'll leave my solution here too. I tried to be concise without sacrificing clarity.

myarray.reduce((a, e, i) => {
  // a: Accumulator; e: current Element; i: current Index
  return a.length > 0 ? [...a, e + a[i - 1]] : [e];
}, []);

Map, Filter, Reduce, Find, Some, etc. are highly underrated.

Upvotes: 3

Anson C
Anson C

Reputation: 507

/**
 * Turn an array of numbers to cumulative sum array
 * @param { Array } [1,2,3,4,5]
 * @return { Array } [1,3,6,10,15]
 */

const accumulate = (a, c) => a + c

const cusum = arr => arr.map((v, i, data) => {
    return data.slice(0, i + 1).reduce(accumulate)
})

Upvotes: 0

p8ul
p8ul

Reputation: 2320

A simple function using array-reduce.

const arr = [6, 3, -2, 4, -1, 0, -5];

const prefixSum = (arr) => {

  let result = [arr[0]]; // The first digit in the array don't change
  arr.reduce((accumulator, current) => {
       result.push(accumulator + current);

       return accumulator + current; // update accumulator
  });
  return result;
}

Upvotes: 1

Jibwa
Jibwa

Reputation: 41

My initial ES6 thought was similar to a few above answers by Taeho and others.

const cumulativeSum = ([head, ...tail]) =>
   tail.reduce((acc, x, index) => {
      acc.push(acc[index] + x);
      return acc
  }, [head])
console.log(cumulativeSum([-1,2,3])

The solution performs:

n lookups, n - 1 sums and 0 conditional evaluations

Most of what I saw above appeared to use:

n lookups, 2n sums, and n conditional evaluations:

You could do this with ie6 safe js as well. This is possibly more efficient since you don't have to create the tail spread array.

function cumulativeSum(a) {
    var result = [a[0]];
    var last = a[0];
    for (i = 1; i < a.length; i++) {
        last = last + a[i];
        result.push(last)
    }
    return result;
}
console.log(cumulativeSum([-1,2,3]))

Upvotes: 2

Vichu Menon
Vichu Menon

Reputation: 11

Another clean one line solution with reduce and concat

var result = myarray.reduce(function(a,b,i){ return i === 0 ?  [b]: a.concat(a[i-1]+b);},0);
//[5, 10, 3, 2] => [5, 15, 18, 20]

Upvotes: 1

Taeho Oh
Taeho Oh

Reputation: 1

Use arrow function instead of function, comma operator instead of return, and currentIndex in reduce callback.

[5, 10, 3, 2].reduce((r, a, i) => (r.push((i && r[i - 1] || 0) + a), r), []); // [ 5, 15, 18, 20 ]

Upvotes: 0

Shablcu
Shablcu

Reputation: 303

Simple solution using ES6

let myarray = [5, 10, 3, 2];
    let new_array = [];  
    myarray.reduce( (prev, curr,i) =>  new_array[i] = prev + curr , 0 )
    console.log(new_array);

For more information Array.reduce()

Arrow function

Upvotes: 10

Prashant Tapase
Prashant Tapase

Reputation: 2147

Simple solution using for loop

var myarray = [5, 10, 3, 2];

var output = [];
var sum = 0;

for(var i in myarray){
  sum=sum+myarray[i];
  output.push(sum)
}
console.log(output)

https://jsfiddle.net/p31p877q/1/

Upvotes: 2

Paul Lucas
Paul Lucas

Reputation: 1615

A couple more options with ES6 array spreading

[1, 2, 3].reduce((a, x, i) => [...a, x + (a[i-1] || 0)], []); //[1, 3, 6]

or

[3, 2, 1].reduce((a, x, i) => [...a, a.length > 0 ? x + a[i-1] : x], []); //[3, 5, 6]

Upvotes: 12

John Hoffer
John Hoffer

Reputation: 54

To keep the cumsum within a function until fully built, I offer this minor variant on Matt's Answer:

var cumsum = function(past_sums, new_value) {
  var last_sum = 1*past_sums.slice(-1);
  var new_sum = last_sum + new_value;
  return past_sums.concat([new_sum]);
}
var some_sums = [5, 10, 3, 2].reduce(cumsum, []);

Here's how it works:

  • The first cycle:
    • past_sums.slice(-1) === []
    • 1*past_sums.slice(-1) === 0
  • All but the last cycle:
    • cumsum returns [past_sums and new_sum] as next cycle's past_sums
  • The last cycle:
    • cumsum returns [5, 15, 18, 20] as the output Array some_sums

It can be written with fewer lines:

var cumsum = function(sums, val) {
  return sums.concat([ val + 1*sums.slice(-1) ]);
}
var some_sums = [5, 10, 3, 2].reduce(cumsum, []);

With Arrow Functions (Not for ≤IE11 or Opera Mini), I'd write this:

var cumsum = (sums,val) => sums.concat([ val + 1*sums.slice(-1) ]);
var some_sums = [5, 10, 3, 2].reduce(cumsum, []);

Upvotes: 0

Flavio
Flavio

Reputation: 544

Returns sorted obj by key and sorted array!!!

var unsorted_obj = {
  "2016-07-01": 25,
  "2016-07-04": 55,
  "2016-07-05": 84,
  "2016-07-06": 122,
  "2016-07-03": 54,
  "2016-07-02": 43
};

var sort_obj = function(obj){
  var keys = [];
  var sorted_arr = [];
  var sorted_obj = {};

  for(var key in obj){
    if(obj.hasOwnProperty(key)){
      keys.push(key);
    }
  }

  keys.sort();

  jQuery.each(keys, function(i, key){
    sorted_obj[key] = obj[key];
    var val = obj[key];
    sorted_arr.push({
      idx: i,
      date: key,
      val: val
    })
  });

  return { sorted_obj: sorted_obj, sorted_arr: sorted_arr };

};

var sorted_obj = sort_obj(unsorted_obj).sorted_obj;
var sorted_arr = sort_obj(unsorted_obj).sorted_arr;

// sorted_arr = [{"idx":0,"date":"2016-07-01","val":25},{"idx":1,"date":"2016-07-02","val":43},{"idx":2,"date":"2016-07-03","val":54},...]
// sorted_obj = {"2016-07-01":25,"2016-07-02":43,"2016-07-03":54,...}

Upvotes: 0

Ed Brown
Ed Brown

Reputation: 11

use reduce to build the result directly and non-destructively.

a.reduce(function(r,c,i){ r.push((r[i-1] || 0) + c); return r }, [] );

Upvotes: 1

Matt
Matt

Reputation: 20796

Javascript's reduce provides the current index, which is useful here:

var myarray = [5, 10, 3, 2];
var new_array = [];
myarray.reduce(function(a,b,i) { return new_array[i] = a+b; },0);
new_array // [5, 15, 18, 20]

Upvotes: 31

georg
georg

Reputation: 215059

A more generic (and efficient) solution:

Array.prototype.accumulate = function(fn) {
    var r = [this[0]];
    for (var i = 1; i < this.length; i++)
        r.push(fn(r[i - 1], this[i]));
    return r;
}

or

Array.prototype.accumulate = function(fn) {
    var r = [this[0]];
    this.reduce(function(a, b) {
        return r[r.length] = fn(a, b);
    });
    return r;
}

and then

r = [5, 10, 3, 2].accumulate(function(x, y) { return x + y })

Upvotes: 2

Pointy
Pointy

Reputation: 414086

Alternative reduce approach that avoids making new arrays:

var result = myarray.reduce(function(r, a) {
  r.push((r.length && r[r.length - 1] || 0) + a);
  return r;
}, []);

There's no need to re-sum the subarrays for each result.

edit less ugly version of the same thing:

var result = myarray.reduce(function(r, a) {
  if (r.length > 0)
    a += r[r.length - 1];
  r.push(a);
  return r;
}, []);

Upvotes: 23

Sachin Jain
Sachin Jain

Reputation: 21852

How about this solution

var new_array = myarray.concat(); //Copy initial array

for (var i = 1; i < myarray.length; i++) {
  new_array[i] = new_array[i-1] + myarray[i];
}

console.log(new_array);

PS: You can use the original array as well. I just copied it in case we don't want to pollute it.

Upvotes: 5

Related Questions