Understanding how map, filter and reduce works

ยท

7 min read

Understanding how map, filter and reduce works

Introduction

In development, we daily face situations in which we have to make calculations based on the content of an array or object in javascript. Well, the traditional way of accessing and mutating values is good at the start of your web development career until you got to know about functional programming and side effects.

Functional programming and side effect

In functional programming changing or altering things is called mutation and the outcome is called side effect. A function ideally should be a pure function meaning that it doesn't cause any side effects.

We can solve almost any array processing problem using map, filter and reduce without mutating or changing the content of the original array.

Map

The map() method iterates over each item in an array and returns a new array containing the return values of calling the callback function on each element.

When callback is used it takes 3 arguments -

  1. current element being processed
  2. index of that element
  3. array upon which map method was called

let's try to understand this with the help of an example.

for simplicity the example only uses the first argument of the callback

const arr = [1, 2, 3, 4, 5];
const squaredArr = arr.map((number) => {
  return Math.pow(number, 2);
});
console.log(arr);
// output -> [1, 2, 3, 4, 5];

console.log(squaredArr);
// output -> [1, 4, 9, 16, 25];
  • In the above example, the map method is iterating over each element of the array arr and passing it to the callback function one-by-one
  • Callback function then taking numbers one by one and returning a squared version of that number.
  • The return values of the callback function is pushed into a whole new array.
  • After all iterations, the map method will return that new array containing the return values of the callback. In this case, the new array is squaredArr.

๐Ÿšจ Things to notice

  • map() method returns a new array of the same length as the one it was called on.
  • it doesn't alter/modify the original array as long as its callback function doesn't.

Writing your own version of map()

To understand how map() works internally it is good to try and write your own version of the map.

let's see how the map method works internally with the help of an example

const arr = [1, 2, 3, 4, 5];

// own version of map
Array.prototype.testMap = function(callback){
  const newArray = [];
  for(let index=0; index < arr.length; index++){
    const currentElement = arr[index];
    const returnValueOfCallback = callback(currentElement);
    newArray.push(returnValueOfCallback)
  }
  return newArray;
};

// callback function
const squaredValueProvider = (number) => Math.pow(number, 2);

// new array
const squaredArr = arr.testMap(squaredValueProvider);

console.log(arr);
// output -> [1, 2, 3, 4, 5]

console.log(squaredArr)
// output -> [1, 4, 9, 16, 25]

In the above example, we have 4 things that are important to understand

  1. Array.prototype.testMap() - our own version of map() method.
  2. arr - array upon which we want to use our testMap() method.
  3. squaredValueProvider - function which takes a number and returns squared version of that number.
  4. squaredArr - new array containing return values of the callback squaredValueProvider.

Inside Array.prototype.testMap() we have declared an empty array named newArr and a for loop for iterating over arr array items.

In for loop we are taking numbers from arr array one-by-one based on index variable of the for loop and storing it in currentElement variable. Then we are passing that current element to the callback function and storing the returned value of that callback into returnValueOfCallback variable and pushing that return value into newArr array.

After all iterations, we are returning newArr to the place where the testMap() method is called in this case squaredArr.

Filter

As the name suggests, it filters the array based on the callback function passed to it. filter() calls a function on each element of an array and returns a new array containing only the elements for which that function returns TRUE.

The callback function accepts 3 arguments -

  1. current element being processed
  2. index of that element
  3. array upon which map method was called

let's try to understand this with the help of an example.

for simplicity the example only uses the first argument of the callback

const numbersList = [1, 2, 3, 4, 5];

const evenNumbersList = numbersList.filter((number) => {
  return (number % 2 === 0);
});

console.log(numbersList);
// output -> [1, 2, 3, 4, 5]

console.log(evenNumbersList);
// output -> [2, 4]
  • In the above example, the filter method is iterating over each element of numbersList array and running the callback function for each element.

  • The callback function is taking numbers from numberList array one-by-one and returning TRUE and FALSE based on the condition we have written inside the callback function. In this case, we are testing whether the number is completely divisible by 2 or not.

  • After testing all the numbers of numbersList array, the filter method will return a new array containing only the numbers for which the callback returns true in this case the new array is evenNumbersList

Writing your own version of filter()

To understand how filter() works internally it is good to try and write your own version of the filter method.

let's see how the filter method works internally with the help of an example

const numbersList = [1, 2, 3, 4, 5];

// own version of filter
Array.prototype.testFilter = function(callback){
  const newArr = [];
  for(let i=0; i<numbersList.length; i++){
    const currentNumber = numbersList[i];
    const responseFromCallback = callback(currentNumber);
    if(responseFromCallback){
      newArr.push(currentNumber);
    }
  }
  return newArr;
}

// callback
const evenNumberTester = number => number % 2 === 0;

// new array
const evenNumbersList = numbersList.testFilter(evenNumberTester);

console.log(numbersList);
// output -> [1, 2, 3, 4, 5];

console.log(evenNumbersList);
// output -> [2, 4];

In the above example, we have 4 things that are important to understand

  1. Array.prototype.testFilter() - our own version of filter() method.
  2. numbersList - array upon which we want to use our testFilter() method.
  3. evenNumberTester - function which takes a number and returns TRUE if the number is completely divisible by 2 and FALSE if not.
  4. evenNumbersList - new array containing only the numbers for which callback returns TRUE.

Inside Array.prototype.testFilter() we have declared an empty array named newArr and a for loop for iterating over numbersList array items.

In for loop we are taking numbers from numbersList array one-by-one based on i variable of the for loop and storing it in currentElement variable. Then we are passing that current element to the callback function and taking response from the callback and storing the response into responseFromCallback variable.

If the response from the callback function for the current variable is TRUE, we will push that element to the newArr array.

After all iterations, we are returning newArr to the place where the testFilter() method is called in this case evenNumbersList .

Reduce

The reduce method iterates over each item in an array and returns a single value (i.e string, number, object, array)

This is achieved via a callback function that is called on each iteration.

The callback function accepts 4 arguments -

  1. accumulator - first argument is known as an accumulator which gets assigned the return value of the callback function from the previous iteration.
  2. current element being processed
  3. index of that element
  4. array upon which map method was called

In addition to callback function, reduce has an additional parameter that takes an initial value of the accumulator.

If this second parameter is not provided, then the first iteration is skipped and the second iteration gets passed the first element of the array as the accumulator.

let's try to understand this with the help of an example.

for simplicity, the example only uses the first 2 arguments of the callback

const numbers = [1, 2, 3, 4, 5];

const sumOfNumbers = numbers.reduce((sum, num) => {
  return sum += num;
}, 0);

console.log(numbers);
// output -> [1, 2, 3, 4, 5]

console.log(sumOfNumbers);
// output -> 15

In the above example inside the callback function, the sum is playing the character of accumulator having the initial value equal to the second parameter of the reduce method in this case sum is initialized with 0.

  • in the first iteration, the value of sum is 0 and num is 1. Then we are adding sum and num and the new value of sum will become 1. Then we are returning this sum value for setting the value of the accumulator for the next iteration.
  • in the second iteration, the value of the sum is 1 which is returned by the callback function from the previous iteration. Again we are adding the current number with the previous value of sum and returning the new sum value for setting the value of the accumulator for the next iteration.
  • This process of updating the accumulator and returning the updated accumulator value for the next iteration will go on until all elements of the array are processed.
  • After all iterations, reduce method will return the value of the accumulator from the last iteration. In this case, the value of accumulator sum in the last iteration is 15.

I hope you enjoyed reading this article and learnt something new today ๐Ÿ˜„

ย