0

This question is come from how-to-sum-an-array-for-each-id-and-create-new-array-in-react,OP needs to sum data by id

const data = [{"id": "One", "number": 100}, 
  {"id": "One", "number": 150}, 
  {"id": "One", "number": 200}, 
  {"id": "Two", "number": 50}, 
  {"id": "Two", "number": 100}, 
  {"id": "Three", "number": 10}, 
  {"id": "Three", "number": 90}]

In order to do it,I just use a traditional way(check with if) to do it with reduce()

let result2 = data.reduce((a, v) => {
  let obj = a.find(i => i.id == v.id);
  if (obj) {
      obj.number += v.number;
  } else {
      a.push(v);
  }
  return a;
}, [])

console.log(result2);

And I found another answer is more elegant(one-liner):

let result3 = data.reduce((acc, {id, number}) => ({...acc, [id]: {id, number: acc[id] ? acc[id].number + number: number}}), {});
console.log(Object.values(result3));

The question is that when I ran the two methods seperately,I can got the expected result(jsfiddle1 and jsfiddle2)

enter image description here

However,if I ran them together,the seconds result(from result3) is not as expected(jsfiddle 3) enter image description here

I do not know why this happen,can anyone help me to analysis this?

Also,I want to know if there are a more elegant one-liner solution to do it.

Thanks in advance!

flyingfox
  • 13,414
  • 3
  • 24
  • 39
  • your method mutates the values in data - if you reverse the order of running the two methods you'll see – Jaromanda X Nov 07 '22 at 02:50
  • @JaromandaX Even if I change the running order,I still got the same result. Can you give a more detail explanation,please? Thanks – flyingfox Nov 07 '22 at 02:50
  • yes, your method changes the values in the original data - `obj.number += v.number` changes `data` - change your code to `a.push({...v})` and it wont – Jaromanda X Nov 07 '22 at 02:51
  • @JaromandaX I changed the order and still got the same result, jsfiddle have added – flyingfox Nov 07 '22 at 02:53
  • @lucumt You need to do a deep copy of the object – HappyDev Nov 07 '22 at 02:56
  • `I changed the order and still got the same result` - yes, the results are the same when you change the order, you are correct, it's only when you do your method first that the second method fails on all three totals - but you don't have a fiddle with the order changed – Jaromanda X Nov 07 '22 at 03:01
  • @HappyDev - a shallow copy of each item will do – Jaromanda X Nov 07 '22 at 03:03
  • @lucumt - `console.log(data)` in YOUR code after the reduce - you'll see it has changed – Jaromanda X Nov 07 '22 at 03:03
  • @JaromandaX Can you post an answer for it,plz? – flyingfox Nov 07 '22 at 03:04
  • @JaromandaX Wouldn't a shallow copy share the same reference as the original object? – HappyDev Nov 07 '22 at 03:07
  • a shallow copy of the item object ... i.e. what I said earlier - `a.push({...v})` - fixes the mutation issue that causes the problem in the first place – Jaromanda X Nov 07 '22 at 03:08
  • @JaromandaX not that it matters much but isn't that a deep copy? – HappyDev Nov 07 '22 at 03:15
  • 1
    @HappyDev No. If `v.arr` was an array, when you say `copy = { ...v }`, the array is the same reference. Any change made to `v.array` or to `copy.array` would show up in the other one. – Norguard Nov 07 '22 at 03:28
  • 1
    @HappyDev - no, it is not [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals) – Jaromanda X Nov 07 '22 at 03:28

2 Answers2

3

Your version mutates, as has been pointed out. It mutates the objects that are in the data array. So if you use the data array after your let result2 = then the data array has been mutated.

If your fiddle looked like:

let result3 = data.reduce(...);
console.log(result3);

let result2 = data.reduce(...);
console.log(result2);

then the answers will be the same values, because the data array doesn't get mutated until your result2 code runs. But any other users of the data array, in the future, would have to deal with the mutated values.

Meanwhile, you can either do what has been recommended in the comments and shallow copy:

let result2 = data.reduce((a, v) => {
  let obj = a.find(i => i.id == v.id);
  if (obj) {
      obj.number += v.number;
  } else {
      const copy = { ...v }; // <-- don't mutate the original
      a.push(copy);
  }
  return a;
}, [])

Another simple way of doing this (exact data set) is to rebuild the data after the fact.

const dictionary = data.reduce((dict, { id, number }) => {
  const currentSum = dict[id] ?? 0;
  dict[id] = currentSum + number;
  return dict;
}, {});

const results = Object.entries(dictionary)
  .map(([id, number]) => ({ id, number }));
Norguard
  • 26,167
  • 5
  • 41
  • 49
0

Another way to archive it is to utilize the Map data structure in js

const result = Array.from(
  data.reduce(
    (acc, next) => acc.set(next.id, next.number + acc.get(next.id) || 0),
    new Map(),
  ),
  ([id, number]) => ({ id, number }),
);
console.log(result)
N_A_P
  • 41
  • 3