2

Say I have the following array:

let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]

I would like to compute the cumulative sum of each key, but I would also like the output to be an array of the same length with the cumulative values at each step. The final result should be:

[{a: 1, b: 2}, {a: 3, b: 6}, {a: 11, b: 5}]

My issue is that I am not able to obtain the array as desired. I only get the final object with this:

let result = arr.reduce((accumulator, element) => {
  if(accumulator.length === 0) {
    accumulator = element
  } else {
    for(let i in element){
      accumulator[i] = accumulator[i] + element[i]
    }
  }
  return accumulator
}, [])

console.log(result); // {a: 11, b: 5}

5 Answers 5

3

What you're after sounds like the scan() higher-order function (borrowing the idea from ramda.js), which allows you to return an accumulated result for each element within your array. The scan method is similar to how the .reduce() method behaves, except that it returns the accumulator for each element. You can build the scan() function yourself like so:

let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}];

const scan = ([x, ...xs], fn) => xs.reduce((acc, elem) => {
  return [...acc, fn(acc.at(-1), elem)];
}, xs.length ? [x] : []);

const res = scan(arr, (x, y) => ({a: x.a+y.a, b: x.b+y.b}));
console.log(res);

You might consider further improvements such as providing an initial value to the scan method (similar to how reduce accepts one). Also, if you need better browser support the .at() method currently has limited browser support, so you may instead consider creating your own at() function:

const at = (arr, idx) => idx >= 0 ? arr[idx] : arr[arr.length + idx];
Sign up to request clarification or add additional context in comments.

Comments

2

You can easily achieve the result using reduce as

let arr = [
  { a: 1, b: 2 },
  { a: 2, b: 4 },
  { a: 8, b: -1 },
];

const result = arr.reduce((acc, curr, i) => {
  if (i === 0) acc.push(curr);
  else {
    const last = acc[i - 1];
    const newObj = {};
    Object.keys(curr).forEach((k) => (newObj[k] = curr[k] + last[k]));
    acc.push(newObj);
  }
  return acc;
}, []);

console.log(result);

Comments

1

Something like this:

const arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]

const result = arr.reduce((accumulator, element, index) => {
  if(accumulator.length === 0) {
    accumulator.push(element)
  } else {
    const sum = {};
    for(let i in element) {
      sum[i] = element[i] + (accumulator[index - 1][i] || 0)
    }
    accumulator.push(sum)
  }
  return accumulator
}, [])

console.log(result);

Another option is keep sum result using a Map, it helps if keys in elements of the array are not always same.

const arr = [{a: 1, b: 2}, {a: 2}, {a: 8, b: -1}];
const map = new Map();
const result = arr.map((element) => {
  const sum = {};
  for (let i in element) {
    sum[i]= element[i] + (map.get(i) || 0);
    map.set(i, sum[i]);
  }
  return sum;
});

console.log(result);

Comments

1

Here is a bit more concise reduce, probably not as readable as a consequence...

array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])

let array = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
let culm = array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])
console.log(culm)
 

Comments

0

Given:

const xs =
  [ {a: 1, b:  2}
  , {a: 2, b:  4}
  , {a: 8, b: -1}];

Define a function sum such as:

const sum = ([head, ...tail]) =>
  tail.reduce((x, y) =>
    ({a: (x.a+y.a), b: (x.b+y.b)}), head);

sum(xs);
//=> {a: 11, b: 5}

Then apply that function in a map on larger slices of xs:

xs.map((_, i, arr) => sum(arr.slice(0, i+1)));
//=> [ {a:  1, b: 2}
//=> , {a:  3, b: 6}
//=> , {a: 11, b: 5}]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.