Once upon a time, a landscape designer turned stay-at-home parent decided to become a software engineer. It may sound far-fetched — to picture such a drastic shift in careers — but the three are actually very similar. Software engineering is a form of design, and the journey to become a programmer via bootcamp is fraught with perils that have the same panicky, fly-by-the-seat of your pants feel as does raising a child. In the same way that you need certain tools and skills to create a functional landscape, or to help navigate a tantrum while picking out an outfit for your 3 year old child, you must also choose the right tools for creating a functional program. Enter, Ruby enumerables.
In the short time I’ve had to absorb and understand Ruby, so far I have learned that enumerables are indispensable. You might be asking, “What the heck is an enumerable?” Great question! In general, enumerables allow us the opportunity to traverse through collections of data, search those collections or sort those collections and return a result. It’s also important to know that your results will vary greatly based on which enumerable is used.
When it comes to enumerables, you will find that there is a long list of options to choose from. Each of them have their own job, and it can be overwhelming to think of what you need to use. If you know anything about me, you know that I can lean on the side of overcomplicating things. So, for both my sake and your sake, I’m going to keep things simple and only talk about two of the most widely used enumerables — .select and .map and how they can be used on the array datatype.
First, some definitions:
.select aka .filter or .find_all
— iterates through a collection of data, filters out the items that return false and returns a new array of all items for which the block returns true. *Does not mutate/change the original array.
*Bonus method: The opposite of the .select method is .reject.
.map aka .collect
—iterates through a collection of data, allowing you to transform that data into new data, and returns a new array with the transformed data. *Does not mutate/change the original array.
Let’s play around with some examples to see how .select and .map behave.
Say we have an array of strings that are detectives. We want to find all of the detectives that have a name that starts with the letter “S”. Here is how we would produce that information:
As you can see, we easily found all of the detectives that had names that began with the capital letter “S”, and these names were returned to us in the form of a new array. We did this by evaluating each item/element in the array to see if it returned true based on our query inside the block.
*Remember, we could have used .filter or .find_all for this task as well, since they are both aliases for .select. It’s just a matter of personal preference as to which name of this method you choose to use.
I’ll show you what it looks like if we use .reject instead of .select:
As you can see, the .reject method removed “Scooby” and “Shaggy” from the array and returned an array without them. Pretty slick!
Let’s say you hired someone to create an array of strings that are detectives. Unfortunately you needed the strings to be capitalized, and they weren’t. (“You had ONE job!”) No worries. Let’s use .map to transform that array to reflect the changes that we want to see:
Isn’t this amazing?! So as you can see, you have now “transformed” your array to your liking. You used .map, passed it some logic inside of your block, and it rewarded you with a new array. So cool! Remember, we could have used .collect for this as well, since it is an alias for the .map method.
Just for fun, let’s see what would happen if we switched our use of .select with .map and vice versa.
First, let’s try using .map to find all of the detectives whose names start with the capital letter “S”:
As you can see here, the use of .map in this instance did not return an array of strings that began with the letter “S”, but instead returned an array of boolean values for each element in the array. By using .map, we were asking it to return true or false if each element satisfied the logic passed into the block. It did exactly that, but what we actually wanted was for it to select those that returned true. That’s why we need to use .select for this logic.
Let’s try using .select to capitalize our detective’s names:
As you can see here, the use of .select in this instance did not transform our elements as we asked it to inside of the block. It simply returned the original array untouched. It had no condition passed into the block of which to compare the elements to, select and return them. So this is why .map should be used for this type of logic.
Finally, let’s consult our handy ruby-docs https://ruby-doc.org/core-2.7.1/Enumerable.html to once again to remind ourselves what .select and .map actually do:
.select — Returns an array containing all elements of
enum for which the given
block returns a true value.
.map — Returns a new array with the results of running block once for every element in enum.
Now that we have solved the mystery of .select and .map, you should have a good foundational understanding of how to iterate through an array with an enumerable, as well as a hash and any combination of the two. Don’t be afraid to check out the Ruby docs and experiment with the many other Ruby iterators out there.
Go forth, be happy and have fun coding!