Javascript array.reduce explanation

I saw this snippet online that have the same scenario as my problem. I want to understand this code. Can anyone please enlighten me how the row which says " res[value.Id].qty += value.qty;" changes the value of the already inserted item in result array.

I am talking about line 14 in this jsfiddle snippet https://jsfiddle.net/1xuamw9f/4/
Line 14 changes the value of the code in Line 12. How does that work?

var array = [
  { Id: "001", qty: 1 }, 
  { Id: "002", qty: 2 }, 
  { Id: "001", qty: 2 }, 
  { Id: "003", qty: 4 }
];

var result = [];
var x = array.reduce(function(res, value) {
  if (!res[value.Id]) {
    res[value.Id] = { Id: value.Id, qty: 0 };
    result.push(res[value.Id])
  }
  res[value.Id].qty += value.qty;
  return res;
}, {});

console.log(result)
console.log(x)

Hi @yyanky!

I moved your topic to the javascript section since this is a javascript question instead of a general question.

You will get more responses here than in general.

Thanks!

1 Like

Thanks madam, im new here. Appreciate it!

I recommend watching as many videos as you can by this guy below. He gets into reduce in episode 3, but you need to understand the concepts from 1 and 2 first.

Plus he’s a real character.

2 Likes

Personally, I wouldn’t use a reduce here. I’d use a foreach since a new array is being created.

Really, I’d only make one of res or result. A reduce shouldn’t have side effects, so if you want to use reduce, ditch the result stuff. Or reformulate as foreach and ditch the res.

Anywho, here are some comments on the reduce function

var x = array.reduce(
  function(res, value) { // This function 'reduces' (sorta) the array
    if (!res[value.Id]) { // If value.Id is not in the result array
      res[value.Id] = { Id: value.Id, qty: 0 }; // then add it to the result array
      // result.push(res[value.Id]) // no idea why this is also here
    }
    res[value.Id].qty += value.qty; // Then update the quantity of value.Id in the array
    return res; // 'return' the res array to be updated for the next array element
  },
  {} // The initial value is an empty object);

Thanks for sharing this Chuck. I’ll definitely hangout in his channel.

This is because result contains a copy of the same item that is in res. When two variables refer to the same object (including arrays), modifying it via one variable will result in both variables containing the modified value.

As a side note, mutating an external value isn’t really a great way to use a reduce. It’s likely to result in confusion because callback functions for methods like reduce, etc are expected to be self-contained and not result in side-effects.

3 Likes

Thanks Jeremy. The result array is created because we want the data structure to match the given array. The product of this reducer has an extra key which is the arrray.id

Sure, but you should either make result or res. Not both.

Yeah, reduce is a confusing one. I think you just have to do it a while to get comfortable with it. I had to explain it to a senior dev once. In some ways it is the most powerful prototype method because you can duplicate all the other prototype methods with it - a fun exercise. Just be patient and work at it. Or, if you really want to understand it, write your own implementation of it.

2 Likes

I’ll toot my own horn and refer to my own post as to why you want to avoid side effects in a reduce() or any other HOF. I call it “The No WTFs Principle”. I should try boiling it down to a pithier explanation. Maybe just an example of what happens and leaving the rest as an exercise.

1 Like

The simplest explanation of reduce use is for when you want to create an item from a different array.

Or in other words when you want to reduce an array to a different value.

This means it’s very powerful: you can reduce an array to a single number. You can reduce an array to a string. You can reduce an array to another array. You can reduce an array to an object (…etc, etc).

This means that what that code is doing is “wrong” since it’s not using it to create a new collection: x is unused while res gets created.
That’s not a smart way to use reduce :slight_smile:

3 Likes

Wow! I really didnt know this. Tried to make a sample for fun https://jsfiddle.net/1xuamw9f/27/

Thanks Ariel!

Thanks Marmiz. Never really thought of that before. I should stick to using these methods for their design purpose.

If the result must be an array, this feels a bit more ideomatic and less bug-prone to me:

var items = [
  { Id: "001", qty: 1 }, 
  { Id: "002", qty: 2 }, 
  { Id: "001", qty: 2 }, 
  { Id: "003", qty: 4 }
];

var reducedItems = Object.values( // Note: '.values' converts map to array
  // Convert array to hashmap of unique 'Id's
  items.reduce(
    (hashMap, item) => {
      // Add new empty item if not found
      if (!hashMap[item.Id]) {
        hashMap[item.Id] = { Id: item.Id, qty: 0 };
      }
      // Update quantity
      hashMap[item.Id].qty += item.qty;
      return hashMap;
    }, {}));

console.log(reducedItems)

Though, this feels even clearer to me:

let hashMap = {};
// Convert array to hashmap of unique 'Id's
items.forEach(
  (item) => {
    // Add new empty item if not found
    if (!hashMap[item.Id]) {
      hashMap[item.Id] = { Id: item.Id, qty: 0 };
    }
    // Update quantity
    hashMap[item.Id].qty += item.qty;
  });

// Note: '.values' converts map to array
let reducedItems = Object.values(hashMap);