0

I'm trying to sum nutritional values nested inside food objects:

[
  {
    id: 'Potato',
    nutrition: [
      { id: 'PROT', val: 4 },
      { id: 'SUGAR', val: 1 }
    ]
  },
  {
    id: 'Banana',
    nutrition: [
      { id: 'FIB', val: 10 },
      { id: 'SUGAR', val: 4 }
    ]
  }
]

I use Array.prototype.reduce() to do so:

foods.reduce((acc, { nutrition }) => {
    nutrition.map((x) => {
      let current = acc.find((y) => y.id === x.id)
      if (current) current.val += x.val
      else acc.push(x)
    })
  return acc
}, [])

This works, but it manipulates my original array and I cannot figure out why. I have tried to use the spread-operator and let/var but to no avail. How can I achieve what I want without side-effects?

howtopythonpls
  • 770
  • 1
  • 6
  • 18
  • 1
    "*it manipulates my original array and I cannot figure out why.*" -> `current.val += x.val` – VLAZ Mar 09 '21 at 14:10
  • @VLAZ okay but how can I create a copy of the food object then? I though "let current" would create a copy – howtopythonpls Mar 09 '21 at 14:14
  • It doesn't. `a = {}; b = a` means there is a single object and both `a` and `b` point to it. Manipulating one, "changes" the other. Well, since they are both literally the same object, you only change one. – VLAZ Mar 09 '21 at 14:17
  • See [Modifying a copy of a JavaScript object is causing the original object to change](https://stackoverflow.com/q/29050004/4642212). – Sebastian Simon Mar 09 '21 at 16:20

2 Answers2

1

You can make use of reduce and forEach and take Object.values in last, something like this:

const foods = [ { id: 'Potato', nutrition: [ { id: 'PROT', val: 4 }, { id: 'SUGAR', val: 1 } ] }, { id: 'Banana', nutrition: [ { id: 'FIB', val: 10 }, { id: 'SUGAR', val: 4 } ] }];

const result = Object.values(foods.reduce((a,{nutrition})=>{
    nutrition.forEach(({id, val})=>{
        (a[id] ??= {id, val:0}).val+=val;
    });
    return a;
},{}));

console.log(result);
gorak
  • 5,233
  • 1
  • 7
  • 19
  • Awesome, thanks! Could you explain (how can I read) '??='? I assume that it's a quick way of creating a key if there is none but have never seen it before so I am not sure. – howtopythonpls Mar 09 '21 at 14:21
  • 2
    @howtopythonpls this is logical nullish assignment, you can read about it more here:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment – gorak Mar 09 '21 at 14:22
1

You can first make use of forEach instead of map since there is no need of map to return a new array in your use-case and fix the above by making a copy of x when pushing to the acc array like so :-

foods.reduce((acc, { nutrition }) => {
    nutrition.forEach((x) => {
      let current = acc.find((y) => y.id === x.id)
      if (current) current.val += x.val
      else acc.push({...x})
    })
  return acc
}, [])
Lakshya Thakur
  • 8,030
  • 1
  • 12
  • 39