Home > Back-end >  Cumulative sum of specific keys with array output using reduce
Cumulative sum of specific keys with array output using reduce

Time:12-07

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}

CodePudding user response:

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);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

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];

CodePudding user response:

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);
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

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);
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

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);
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

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)
 
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related