3
votes

Who can kindly explain the accumulator acc construction below in plain English?

return arr1.reduce(function(acc, curr){  
      var last = acc[acc.length-1];  
        if(acc.length > 0 && curr[1]===last[1]) {
        last[0] += curr[0];
      } else acc.push(curr);
      return acc;
      }, []);
    }

This reduce method can be used to solve the FreeCodeCamp "Inventory Update" assignment, as part of their advanced algorithm scripting lessons.

One is required to update existing items in an inventory array (arr1) with new items in a "new delivery" array.

Two test arrays, a present inventory curInv and a new delivery newInv respectively, could be as follows:

var curInv = [
    [21, "Bowling Ball"],
    [2, "Dirty Sock"],
    [1, "Hair Pin"],
    [5, "Microphone"]
];

var newInv = [
    [2, "Hair Pin"],
    [3, "Half-Eaten Apple"],
    [67, "Bowling Ball"],
    [7, "Toothpaste"]
];

After finding several excellent articles on Javascript reduce method (such this post and a great video course on egghead.io), and somehow sensing the power it is harnassing, I would read the method as follows:

Reduce the inventory array, by creating an empty array [ ] first (the initial value), then applying the following callback function:

If the inventory array is currently not empty (has a length greater than zero), and the name of currently handled item (index 0 of curr could read "Bowling Ball" for example) is identical to the last item of the inventory array being updated, then update the amount of this item in the inventory array.

The last item is defined right above the if statement, as follows: take the present length of the accumulated array upto now, subtract 1, and use this value to index the accumulated array. The element at that index is then assigned to the variable 'last'.

On the other hand, if the inventory is empty, add the new item entirely, - that is: item name and amount. Now return the newly accumulated array."*

How is using the length - 1 of the accumulator useful to make acc actually accumulate? (pardon the alliteration)

I think I understand most of how this reduce method is built, but please correct me wherever I'm misreading), except this particular use of acc.length-1.

Cheers, k.

3
how is this reducer used to solve this? can you share the actual call?thedude
@thedude: one solution appears to be combining reduce method with concat and sort alphabetically like this Repl.it linkJuchtmans Chris

3 Answers

0
votes

The actual solution involves concatenating and sorting both arrays and only then reducing them. In this case whenever we evaluate a new item, if its name is not equal to the last accumulator item, it means it's a new item.

Using your example, the list we are reducing is:

[
  [ 21, 'Bowling Ball' ],
  [ 67, 'Bowling Ball' ],
  [ 2, 'Dirty Sock' ],
  [ 1, 'Hair Pin' ],
  [ 2, 'Hair Pin' ],
  [ 3, 'Half-Eaten Apple' ],
  [ 5, 'Microphone' ],
  [ 7, 'Toothpaste' ]
]

so when we encounter the second item, the last value in the accumulator is [21, 'Boweling Ball'] and when we compare the strings we go into the first condition.

0
votes

Are you asking about this section?

var last = acc[acc.length-1]; 

If so acc.length-1 is so because in an array

acc = [a,b,c,d]
acc.length is equal to 4

to access element d you will access it via

acc[3];  //which equals d

That is because we count positions from 0,1,2,3

0
votes

tl;dr: Check out this REPL example


Reduce is capable of reducing the original array to a value that is defined on the function.

For instance, if we had an array of integers that we wanted to compute the total sum of it, we could use reduce to achieve that.

Of course, you might ask why would you ever want to use reduce as opposed to a for loop?

The primary reason why reduce is a better choice is because the reducer function that we pass as a parameter is pure. That means, there are no 'extra' nor 'external' variables that are needed in order to compute the result. In a for loop, we would need to have a totalSum variable to compute the result, whereas in the reduce version this is not needed.

Back to the OP's example, this is how we could construct the reduce to group by the invites by the same names:

// reduce example
const reducer = (accumulator, currentValue) => {
    if (accumulator.length === 0) {
        accumulator.push(currentValue);
        return accumulator;
    }

    const index = accumulator.findIndex(el => el[1] === currentValue[1]);

    if (index !== -1) {
        accumulator[index][0] += currentValue[0];
        return accumulator;
    }
    accumulator.push(currentValue);
    return accumulator
};

Here's the equivalent version with the for loop:

// for loop example
let withForLoop = (input) => {
    let res = [];
    for (let i = 0; i < input.length; i++) {
        let currInput = input[i];
        const index = res.findIndex(el => el[1] === currInput[1]);
        if (index !== -1) {
            res[index][0] += currInput[0];
            continue;
        }
        res.push(currInput);
    }
    return res;
}

If you are new to reduce, it might take some time until you completely understand it. Its usage almost always leads to less bug-prone applications, thus I would advocate for taking some time to fully understand its capabilities and when to use it.