The Beginners Guide to Understanding .map & .filter Enumerables for Ruby
Welcome, my fellow programmer! I’m glad you found this article in your search to understand two enumerables that you will encounter and use quite often on your Ruby journey. I hope you will be able to take from this a better understanding of the .map and .filter enumerables so that you can use both with confidence. Most importantly, I hope you know that you’re not alone and that I chose to write about this because I was also in the same boat as you are in currently. Let’s start by describing the function of each enumerable using some examples. Once we get the fundamentals down, we can demonstrate both in a more complex or practical setting.
.map (or .collect)
Just like all enumerables, .map will iterate through every object in an array, hash, or range. It is primarily used to transform data in an object. Those changes will be returned in a new array. Let’s look at an example:
In the example above, we start by creating an array of integers — (1–5) called numbers. Now, let’s say that we want a new array with all of the integers in our numbers array to be multiplied by 3. We can use the .map enumerable to make that change to each number and return a new array with those changes. We begin by calling the .map enumerable by attaching it to the end of our array, numbers. But, we need to tell the enumerable — (.map) what changes we want to make to each integer it iterates over. So, we create a do/end statement, using curly braces — ({}). Within our statement we start by defining each number with the letter n — (|n|). Once each integer is defined as n, we tell our .map enumerable to multiply every integer, n, by 3 — (n * 3). After we end our do/end statement, our .map enumerable will have all of the information required to provide a new array with each integer from the numbers array multiplied by 3. As you can see above this is exactly what is returned when the code is run.
.filter (or .select)
Similar to the .map enumerable, .filter will iterate over every object in an array, hash, or range. However, unlike .map, .filter is used to separate out data based on certain conditions that we set. Just like our .map enumerable, .filter will return a new array, but with data that matches the established condition. Let’s look at an example:
In our example, we see our old friend the numbers array, which we used for our .map example above. But now, let’s say we want to retrieve all of the integers in the array that are greater than 3. How can we filter out the numbers that match that condition? Well, if my bolding wasn’t enough of a clue, we use the .filter enumerable! So just like.map we must call the .filter enumerable on our numbers array — (numbers.filter). But, how can we establish our condition — (numbers greater than 3) for the .filter enumerable on the numbers array? Well, just like the .map method above, we will use a do/end statement for the .filter method to set our condition. Within the statement, we begin by defining each integer as n — (|n|) like we did with our .map example and we put in our condition, numbers greater than 3 — (n > 3). Our statement, {|n| n > 3} translates as, for every number, n, return only the integers that are greater than 3. When we run the method, we see in our example that a new array is returned with the numbers 4 and 5.
HEAT CHECK: What if we tried flipping these examples around?
This is our HEAT CHECK! How are you feeling so far? Hopefully these enumerables are starting to make more sense to you. I like to call this the heat check because we’re just starting to get hot on our knowledge about .map and .filter, but let’s see if we can take it a step further? Can we get HOTTER!? Well, if you’re like me, you might be asking yourself: what would happen if I tried to filter information using .map and transform an array using .filter? Well my curious friend, let’s try it!
As you can see from the example, when we set the .map enumerator with a condition, it will return a boolean — (value of true or false) for every number it iterates through. Every number will be treated as a conditional statement thus, if the number matches the established condition, .map will return true, otherwise it will return false.
Additionally, when we try the .filter enumerable with a statement that is trying to change each number in the numbers array, .filter will return the array, unchanged. This happens because when we use the .filter method, it will return an array containing the items where each value in the object being filtered returns true. Since we are not returning a boolean value from our method — (n * 3), the value that it is iterating over: (n) is converted to a boolean and the object reference is automatically considered true. Thus, it returns a new array that contains all the unchanged values from the original array since each number, n, is returning true.
Heating Up: Let’s Try Them Out
Now that we’ve gained a better understanding of how both the .map and .filter enumerables work, I think it’s time to practice implementing them in a more practical example that will demonstrate our comprehension of the two functions.
Let’s say that you’re a programming professor at Awesome University and you have a class of 20 students. We build a student class so we can develop methods to call upon when we need information about our students. We can look at our class below.
Student #graduate method (implementing the .map enumerable)
Let’s say you want to change the grade of each of your seniors to “Graduate” because they have passed your course. What enumerable can we use to change the grade of our seniors? YES! You’re right, the .map enumerable! Let’s make our method!
In the method above, we begin by creating a class method called graduate that we can call on the student class to access all of our student instances. Within the method we start by using our .map enumerable to iterate over all of our students — (self.all). Next, like we did in our .map example above, we need to tell our method what we want to change in our student instances using a do/end statement. In our do/end statement, we define each student instance with the word student — (|student|). Next, we will use a conditional statement to make sure that only the seniors in your class have their grade changed — (if student.grade == “Senior’). If the student instance passes the conditional statement, we want the grade for that student to change to “Graduate” from “Senior” — (student.grade = “Graduate”). Lastly we end out conditional statement and our do/end statement. Let’s test our method!
In our test above, we start by looking at all of our student instances with the Student.all class method. We see that all of our seniors still have their grades listed as “Senior”. Now let’s use the graduate method we built in our class above to change those grades to “Graduate”. In our example above, we call the method — (Student.graduate) to make this change. To check that our changes have been made we call Student.all class method once more. What’s returned is an array of students with the students listed as “Seniors” prior to the graduate method being called, changed to “Graduate”. Exactly what we wanted!
Student #find_graduates_over_21 method (implementing the .filter enumerable)
Great job so far! You’re starting to get the hang of it! Now, let’s say we want to have a congratulatory drink with our graduating seniors. What enumerable could we use to return a new array with only the names of the graduates in your class over the age of 21? All right all-star, we get it, you got this. You’re right, we should use the .filter enumerable! Let’s build out that method!
Looking at the method above, we begin by creating and defining a class method, find_graduates_over_21. We then call upon all of our student instances — (self.all) and add our .filter enumerable in order to select certain student instances matching a specified condition. All right pro, I know you already know how we will provide our enumerable a condition. The do/end statement! We start the statement by defining each student instance with the word student — (|student|). Then we give our statement a condition to pass. Since we want all of our students to be 21 years of age or older, we write our condition, student.age > = 21. However, not only do we want the students to be 21 and older, we also want them to be graduates! So we include a boolean operator, and — (&&), to make sure that the students we select also have a grade equal to graduate — (student.grade == “Graduate”). We can find our graduates thanks to our awesome graduates class method above, which provided us the changes to our student grades that identify the graduates in your class! We finish the method by ending the statement and our method. Now let’s test out our method!
You can see in the terminal above, after we call the pry method in our console.rb file — (testing file), we call our Student.all method. We can see that all of the changes from our graduate class method are still in place. Sweet! Now, we call on our new class method, find_all_graduates_over_21, to filter out all of our students that are graduating and also 21 years of age or older. Our result is an array of all the students who match the criteria set in our class method, find_all_graduates_over_21. It works! Now you can take these graduates out to a happy hour and send them off into the world the right way!
Final Remarks
It looks like our fun-dacational journey has come to an end, unfortunately. We did so much together, like defining the purpose of our .map and .filter enumerables and seeing each used in an example. We also practiced using them in a realistic situation with our imaginary programming class, which I have included access to the final student class here. I commend you for stopping to read this to better understand these enumerables. I know they were hard for me to grasp at first, but with a lot of practice, I got there and now I’m writing a blog post about them! If they’re still a little fuzzy on these enumerables, don’t fret, it will come to you! Just keep practicing and build up that confidence to use them without hesitation. The amazing thing about programming is that you can test EVERYTHING, which makes practicing a lot easier. If you keep practicing, I know you’ll be able to code more confidently with these enumerables!