A Common Sense Explanation of JavaScript Array Methods

kias.jpg

The array methods filter, map, reduce have been around for a while and they make working with arrays easier. But sometimes it is hard for new JavaScript developers to determine which of the array methods they need to use.

In this post, I will give a simple explanation of the aforementioned array methods. After going through this post, choosing which one to use will be a piece of cake.

What's in an array?

Consider this array:

const balls = [
 {brand: 'Adidas', color: 'blue', rating: 5},
 {brand: 'Konami', color: 'red', rating: 4},
 {brand: 'Verah', color: 'blue', rating: 2},
 {brand: 'Adidas', color: 'green', rating: 3}
]

In the balls array above, the length is 4 (we can get this via balls.length). Every item has a shape: a brand, a color and a rating. When we use any of the array methods mentioned, we are going to be dealing with the length and/or the shape of the array.

Keep in mind…

When we use the filter, map and reduce array methods, we loop through the array. And at every iteration of the loop, we ‘pick’ each member of the array and do something to/with it. In order to “do something to/with” an element of the array, we pass a callback to the array method. A callback is just a function specifying what we want to do with/to the array element - the callback must return something.

For example, if arr is an array:

arr.map(callback)// callback is a function
arr.filter(callback)

The filter method

When we use the filter method on an array, we change the size but not the shape of the array.

Imagine you had a physical box containing those 4 balls described by the array above, each ball has a tag indicating the rating attached to it.

1.png

Now, imagine I give you another box and asked you to put the blue balls inside this new box. If you decide to follow my instructions, you will have a box consisting of 2 balls:

2.png

If we are to represent this new box in code, you have:

[
 {brand: 'Adidas', color: 'blue', rating: 5},
 {brand: 'Verah', color: 'blue', rating: 2}
]

Keep it in mind that before you could filter the box, I had to give you a condition to check for : the ball must be blue. Also, the returned array is a subset of the original.

Now let’s write a filter method in JavaScript:

let blueBalls = balls.filter(ball => ball.color === 'blue')

Simple.

Remember that I mentioned that the callback function must return something? Arrow functions allows us return a value if we write the function on a single line. The code above can also be written as:

balls.filter(function(ball) {
  return ball.color === 'blue'
})

We loop through the ‘balls’ box, pick each ‘ball’ and check if it is blue (ball.color === blue), if it is we dump it into the new box (blueballs)!

In more correct terms, on each iteration of the loop, any item which returns true for ball.color === 'blue' is returned in the new array.

The best part is that our original balls array is still intact. If we console.log(balls) we still have the original 4 balls there. filter (like map and reduce) do not change the array they work on! They return the result without mutating (a.k.a changing) the array.

The map method

The map method changes the shape of an array but not its size. That is, you can use it when you need to modify each member of an array and return the modified array.

For example, let’s say I hand over the balls ‘box’ to you again and asked you to get the ratings on each of our balls into another box. That is: you pick each ball, copy only the rating tag and dump it into another box.

You will have something like this:

3.png

You still have 4 items in the box, but now they have been modified. The shape changes but the size doesn’t.

Let’s write the map function in code.

let tags = balls.map(ball => ball.rating)
console.log(tags)
// returns [5, 4, 2, 3]

The code could also be written as:

let tags = balls.map(function(ball) {
   return ball.rating
})
console.log(tags)

Aha!

Our map method (just like filter) runs a loop through the ‘box’, ‘picks’ a ball and then copies the rating tag! (ball.rating means ‘get me the value of the rating property of this ball’).

If we console.log(balls), we still have our balls array intact as the original. Again, the map method does not mutate the original array! Awesome!.

The reduce method

The reduce array method is quite tricky yet powerful, this is because when we use it, we have dual powers: we can change both the shape of the array and its length. Just like filter and map, the reduce method does not mutate the array we apply it to, it returns a new array.

Remember Compound Interest?

I think of the reduce function in terms of compound interest. In compound interest, the invested money(a.k.a principal) increases each time the interest is computed. For example, if you invest N10, 000 (the principal) at 10% compound interest for 5 years. At the end of the first year, you have an interest of (10% * 10, 000), which is N1 000. This interest is added to the original principal.

So, at the beginning of the second year, the principal is N11, 000. By the end of the second year, the interest is 10% * 11, 000 which is N1, 100. This is added to the principal again to become (11, 000 + 1,100) N12, 100. This becomes the new principal for the third year! This way, the principal grows! This continues to the end of the fifth year!

Side Note: Einstein called compound interest “one of the most powerful forces in nature” 🤓

Back to the reduce method…

Let’s leave the math world of compound interest and travel back to that of the reduce method. What you should carry along from the compound interest world, is that we have a principal, which increases each time we compute the interest and the incremented value becomes the principal for the new investment year.

Just like the filter and map methods, we also pass a callback function to the reduce method, this callback function is called the reducer function. The reducer function takes four arguments but we will be talking about two, which are the Accumulator and the Current Value.

the_array.reduce(callback)

Remember that in compound interest, we started with an initial principal, similarly, the reduce method takes an optional second argument after the callback function, this is the initial value of the accumulator.

the_array.reduce(callback , initialValue)

The initial value is optional. If we don’t specify it, the reduce method takes the first element of the array as the initial value of the accumulator.

the_array.reduce(function(acc, cur){
// do some stuff...
}, initialValue)
// using arrow functions instead 
the_array.reduce((acc, cur) => {
// do some stuff...
}, initialValue)

The accumulator is similar to the principal of compound interest, in the sense that, after every iteration, it changes. And the new value becomes the accumulator for the next iteration. The current value is similar to the interest in compound interest, in the sense that, after every iteration, it’s ‘added’ to the accumulator. (Note: we can perform other actions than addition).

To use the reduce method, let’s try to get the sum of all the ratings in the balls array. To simplify things, let’s start with the tags array from the map method above.

console.log(tags)
// returns [5, 4, 2, 3]

The reduce method is applied thus:

let sumOfRatings = tags.reduce((acc, cur) => acc + cur, 0)
console.log(sumOfRatings)
// returns 14

The initial value of the accumulator (acc) is 0, as we loop through the array, each element of the array is the current value (cur), this is added to the accumulator. The new value of the accumulator will be used in the next iteration of the loop and by the time the loop ends, the value of the accumulator is returned.

Simple Quiz: What’s returned if the initial value is set to 6?

Another example please…

Suppose we have a larger balls array:

const balls = [
 {brand: 'Adidas', color: 'blue', rating: 5},
 {brand: 'Konami', color: 'red', rating: 4},
 {brand: 'Verah', color: 'blue', rating: 2},
 {brand: 'Adidas', color: 'green', rating: 3},
 {brand: 'Konami', color: 'white', rating: 1},
 {brand: 'Adidas', color: 'blue', rating: 2},
 {brand: 'Verah', color: 'yellow', rating: 1},
 {brand: 'Verah', color: 'green', rating: 2},
 {brand: 'Adidas', color: 'blue', rating: 5},
 {brand: 'Konami', color: 'red', rating: 4},
 {brand: 'Adidas', color: 'green', rating: 3},
 {brand: 'Konami', color: 'pink', rating: 1},
]

And Suppose we want to get the number of times each brand is present, we can do this using the reduce method:

const brandFrequency = balls.reduce((acc, cur)=> {
let count = acc[cur.brand] || 0 
  return {
    ...acc,
    [cur.brand]: count + 1
  }
}, {})
console.log(brandFrequency)
//returns {Adidas: 5, Konami: 4, Verah: 3}

Notice that the initial value is an empty object. This is the first value of the acc argument.

I give an explanation of each line below:

...
let count = acc[cur.brand] || 0
...

This line means that each time, we check to see if a brand has been ‘accumulated’ into the acc and has a count value, if it doesn’t, we set its count to 0.

return {
    ...acc,
    [cur.brand]: count + 1
  }

The line above means that after each iteration, we return an object that contains the current value of the accumulator and also we include a brand and increment its count by 1. If the brand is already present in the acc object, it is not re-included, its count is just incremented by 1.

Conclusion

The filter, map and reduce array methods make it possible to write elegant and shorter codes. They are tools introduced to make the developer’s work easier and not frustrate him.

And I hope I have been able to help clarify stuff.

Happy Coding.

Thank you for reading this far. If you enjoyed this post, please share, comment. Maybe it will help someone else too.

Follow me on Twitter if you’re interested in more in-depth and informative write-up like these in the future!

Au revoir!