Home > Mobile >  Is there a better way to sum up values of a nested array?
Is there a better way to sum up values of a nested array?

Time:10-01

I currently have the following items and implementation below. I am not sure if using reduce within another reduce is performant. Is there a better way to sum up nested arrays?

const items = [
  {
     id: 111,
     fruits: [
        {
           name: "apple",
           total: 5
        },
        {
           name: "pineapple",
           total: 1
        }
     ]
  },
  {
     id: 222,
     fruits: [
        {
           name: "kiwi",
           total: 2
        }
     ]
  }
];

// my implementation using reduce within another to get the sum of totals.

const sumOfFruits = items
  .reduce((sum, curr) => sum   curr.fruits
  .reduce((fruitSum, fruitCurr) => fruitSum   fruitCurr.total));

console.log(sumOfFruits);

// returns 8

CodePudding user response:

A cleaner (not necessarily faster) way to do this would be:

  • collect all "fruits" (flatMap)
  • pick "totals" (map)
  • sum the result

const items = [
    {
        id: 111,
        fruits: [
            {
                name: "apple",
                total: 5
            },
            {
                name: "pineapple",
                total: 1
            }
        ]
    },
    {
        id: 222,
        fruits: [
            {
                name: "kiwi",
                total: 2
            }
        ]
    }
];

//

res = items
    .flatMap(x => x.fruits)
    .map(x => x.total)
    .reduce((s, n) => s   n, 0)

console.log(res)

Regarding performance, the thing about javascript is that it's performant enough unless you have millions of objects. And if you do have millions of objects, you shouldn't be using javascript in the first place.

CodePudding user response:

Your code does not produce the desired output: it coerces an object to string and performs string concatenation.

This is because reduce is called without second argument, and thus the accumulator gets the first object as value, while you want the accumulated value to be a number.

So you need to add 0 as second argument for the outer reduce call. For the inner reduce call you can improve a little bit and provide sum as initial value. That way you don't have to do sum anymore.

You can also make use of destructuring in the callback parameters:

This leads to the following code:

const items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];

const sumOfFruits = items.reduce(
    (sum, {fruits}) => fruits.reduce(
        (fruitSum, {total}) => fruitSum   total,
        sum // Continue with the sum we already have
    ), 0 // Start with 0 for the accumulator
);

console.log(sumOfFruits); // 8

Many would agree that this is how it should be done. If performance really is an issue, then you can get a slight improvement by using plain old for loops. These do not use callbacks, and so can be expected to do the job a little bit faster, but with less elegant code. In my experience they also perform a tiny bit faster than for..of loops:

var items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];

var sumOfFruits = 0;
for (var i = 0; i < items.length; i  ) {
    var fruits = items[i].fruits;
    for (var j = 0; j < fruits.length; j  ) {
        sumOfFruits  = fruits[j].total;
    }
}
console.log(sumOfFruits); // 8

It probably is not worth the millisecond you would gain from this with normal sized input.

  • Related