Mapping, Filtering, and Reducing Things in an Array

by kirupa   |   17 August 2017

Earlier, we looked at Arrays and how they make it easy to store a collection of data. We looked at several ways to add items, remove items, and other basic bookkeeping tasks. One of the other things Arrays bring to the table is really simple ways for you to manipulate the data that is contained inside them. These simple ways are brought to you via the map, reduce, and filter methods, and we'll learn all about them in this tutorial.

Onwards!

The Old School Way

Before we talk about map, reduce, and filter and how they make accessing and manipulating data inside an array a breeze, let us look at the non-breezy approach first. This is an approach that typically involves a for loop, keeping track of where in the array you are, and shedding a certain amount of tears.

To see this in action, let's say we have an array of names:

var names = ["marge", "homer", "bart", "lisa", "maggie"];

This aptly named names array contains a list of names that are currently lowercased. What we want to do is capitalize the first letter in each word to make these names look proper. Using the for loop approach, this can be accomplished as follows:

var names = ["marge", "homer", "bart", "lisa", "maggie"];

var newNames = [];

for (var i = 0; i < names.length; i++) {
    var name = names[i];
    var firstLetter = string.charAt(0).toUpperCase();
    
    newNames.push(firstLetter + names.slice(1));
}

console.log(newNames);

Notice that we go through each item, capitalize the first letter, and add the properly capitalized name to a new array called newNames. There is nothing magical or complicated going on here, but you'll often find yourself taking the items in your array, manipulating (or accessing) the items for some purpose, and returning a new array with the manipulated data. It's a common enough task with a lot of boilerplate code that you will keep replicating unnecessarily. In large codebases, making sense of what is going on in a loop adds unnecessary overhead. That's why map, filter, and reduce were introduced. You get all the flexibility of using a for loop without the unwanted side effects and extra code. Who wouldn't want this?!

Modifying Each Array Item with Map

The first of the array methods we will look at for manipulating our array data is map. We will use the map method to take all the items in our array and modify them into something else into an entirely new array:

The way you use it looks as follows:

var newArray = originalArray.map(someFunction);

This single line looks nice and friendly, but it hides a lot of complexity. Let's de-mystify it a bit. The way map works is as follows: You call it on the array that you wish to affect (originalArray), and it takes a function (someFunction) as the argument. This function will run on each item in the array - allowing you to write code to modify each item as you wish. The end result is a new array whose contents are the result of someFunction having run and potentially modified each item in the original array. Sounds simple enough, right?

Using map, let's revisit our earlier problem of taking the lowercased names from the array and capitalizing them properly. We'll look at the full code first and then focus on the interesting details next. The full code is as follows:

var names = ["marge", "homer", "bart", "lisa", "maggie"];

function capitalizeItUp(item) {
    var firstLetter = item.charAt(0).toUpperCase();
    return firstLetter + item.slice(1);
}

var newNames = names.map(capitalizeItUp); 
console.log(newNames);

Take a moment to see how this code works. The interesting part is the capitalizeItUp function that is passed in as the argument to the map method. This function runs on each item, and notice that the array item you are currently on is passed in to this function as an argument. You can reference the current item argument via whatever name you prefer. We are referencing this argument using the boring name of item:

function capitalizeItUp(item) {
    var firstLetter = item.charAt(0).toUpperCase();
    return firstLetter + item.slice(1);
}

Inside this function, we can write whatever code we want to manipulate the current array item. The only thing we need to do is return the new array item value:

function capitalizeItUp(item) {
    var firstLetter = item.charAt(0).toUpperCase();
    return firstLetter + item.slice(1);
}

That's all there is to it. After all of this code runs, map returns a new array with all of the capitalized items in their correct locations. The original array is never modified, so keep that in mind.

Meet Callback Functions

Our capitalizeItUp function is also known more generically by another name. That name is callback function. A callback function is a function that does two things:

  1. It is passed in as an argument to another function
  2. It is called from inside the other function

You will see callback functions referenced all the time...such as when we look at filter and reduce in a few moments. If this is the first time you are hearing about them, you now have a better idea of what they are. If you've heard of them before, well...good for you!

 

Filtering Items

With arrays, you'll often find yourself filtering (aka removing) items based on a given criteria:

For example, let's say we have an array of numbers:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

Right now, our numbers array has both even numbers as well as odd numbers. Let's say we want to ignore all of the odd numbers and only look at the even ones. The way we can do that is by using our array's filter method and filtering out all of the odd numbers so only the even numbers remain.

The way we use the filter method is similar to what we did with map. It takes one argument, a callback function, and this function will determine whether each array item will be filtered out or not. This will make more sense when we look at some code. Take a look at the following:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

var evenNumbers = numbers.filter(function(item) {
   return (item % 2 == 0);
});

console.log(evenNumbers);

We create a new array called evenNumbers that will store the result of filter running on our numbers array. The contents of this array will be the even numbers only thanks to our callback function checking each item to see whether the result of item % 2 (aka checking if the remainder when you divide by 2) is 0. If the callback function returns a true, the item is carried over to the filtered array. If the callback function returns false, the item is ignored.

One thing to note here is that our callback function isn't an explicitly named function like our capitalizeItUp function we saw earlier. It is simply an anonymous one, but it still gets the job done. You'll see this anonymous form commonly where a callback function needs to be specified, so become familiar with this style of defining a function.

Getting One Value from an Array of Items

The last array method we will look at is reduce. This is a bizarre one. With both map and filter, we went from one array with a starting set of values to another array with a different set of values. With the reduce method, we will still start with an array. What we will end up with will be a single value:

This is definitely one of those cases where we need an example to explain what is going on.

Let's reuse our numbers array from earlier:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

What we want to do is add up all the values here. This is the kind of thing the reduce method was built for where we reduce all the values in our array into a single item. Take a look at the following code:

var total = numbers.reduce(function(total, current) {
    return total + current;
}, 0);

console.log(total);

We call reduce on our numbers array, and we pass in two arguments to it:

  1. The callback function
  2. Initial value

We start our summing at an initial value of 0, and our callback function is responsible for adding up each item item in the array. Unlike earlier where our callback function took only the current array item as its argument, the callback function for reduce is slightly more involved. You need to deal with two arguments here as well:

  1. The first argument contains the total value of all the actions you've done so far
  2. The second argument is the familiar current array item

By using these two arguments, you can easily construct all sorts of scenarios involving keeping track of something. In our example, since all we want is sum of all items in the array, we are summing up the total with the value of current. The end result will be 31.

More on the Callback Function Arguments

For our callback functions, we've only specified one argument representing the current array item for map and filter. We specified two arguments representing the total value as well as the current item for reduce. Our callback functions have two optional arguments you can specify:

  1. The current index position of your current array item
  2. The array you are calling map, filter, or reduce on

For map and filter, these would be the second and third arguments you specify. For reduce, it would be the third and fourth arguments. You may go your entire life without ever having to specify these optional arguments, but if you ever run into a situation where you need them, you now know where to find it.

We are almost done here. Let's look at an example that shows the output of reduce to be something besides a number. Take a look at the following:

var words = ["Where", "do", "you", "want", "to", "go", "today?"];

var phrase = words.reduce(function(total, current, index) {
    if (index == 0) {
        return current;
    } else {
        return total + " " + current;
    }
}, "");

console.log(phrase);

In this example, we are combining the text-based content of our words array to create a single value that ends up showing Where do you want to go today? Notice what is going on in our callback function. Besides doing the work to combine each item into a single word, we are specifying the optional third argument that represents our current item's index position. We use this index value to special case the first word to deal with whether we insert or not insert a space character at the beginning.

Conclusion

As the last few sections have highlighted, the map, filter, and reduce methods greatly simplify how we work with arrays. There is another HUGE thing that these three methods scratch the surface of. That thing is something known as functional programming. Functional programming is a way of writing your code where you use functions that:

  1. Can work inside other functions
  2. Avoid sharing or changing state
  3. Return the same output for the same input

There are more nitpicky details that we can list here, but this is a good start. Anyway, you can see how functional programming principles apply to the various callback functions we've used so far. Our callback functions match these three criteria perfectly, for they are functions that can be dropped into or out of any situation as long as the arguments still work. They definitely don't modify any state, and they work fully inside the map, filter, or reduce methods. Functional programming is a fun topic that needs a lot more coverage than what we've looked at in the last few sentences, so we'll leave things be for now and cover it in greater detail in the future.

If you have a question about this or any other topic, the easiest thing is to drop by our forums where a bunch of the friendliest people you'll ever run into will be happy to help you out!

THE KIRUPA NEWSLETTER

Get cool tips, tricks, selfies, and more...personally hand-delivered to your inbox!

( View past issues for an idea of what you've been missing out on all this time! )

GOT A QUESTION?

HOT FORUM TOPICS

Serving you freshly baked content since 1998!

Killer hosting by (mt) mediatemple

Facebook Twitter Youtube Pinterest Instagram Github
BACK TO TOP
new books - yay!!!