Ruby Enumerables: The 8th Wonder of the World

Emily Gann
Analytics Vidhya
Published in
5 min readNov 13, 2019

When I was first learning Ruby, nothing brought me greater angst than having to discern which enumerable to use. Just when I thought I had the hang of it, the enumerable gods slapped me and laughed in my face.

In an attempt to quash my unease and get to the bottom of this elusive topic, I decided to break things down, starting with the actual word itself. According to Merriam-Webster:

Enumerate: (1) to ascertain the number of, or count; (2) to specify one after another, or list

At its simplest, enumerables review each element in an array or hash to determine whether it meets the criteria specified in the code block. If the element meets the criteria, the element is targeted for output. However, the output is determined by the specific enumerable used, for they each behave differently.

What follows is a beginner’s attempt to explain how some common methods––each, map, find, and select ––work as enumerables to analyze and output elements from arrays (I’m focusing on arrays for this article for simplicity’s sake).

#each

Essentially, #each is a generic method that applies the code block to each element in an array. Let’s look at an example:

numbers = [100, 200, 300]
numbers.each do |n|
text = "I want #{n} pieces of chocolate!"
puts text
end

Here we have an array of numbers: [100, 200, 300]. On the next line, I use #each to iterate through all three elements in the array one at a time. I use “n” as my code block variable through which to interpolate each number as it is passed into the sentence that is output. The result:

I want 100 pieces of chocolate!
I want 200 pieces of chocolate!
I want 300 pieces of chocolate!

Unlike other enumerables, #each does not collect the results of running the array items through the code block. In the example above, other than including the interpolated number into the output sentence, there is no indication of what the results are from the enumerable’s work.

Again, #each is the most simplistic of these methods. Want something a little more exciting? Read on…

#map

It took me a little while to fully grasp this one because the name, at least in my opinion, doesn’t reflect its function. When applied to an array, #map returns a new array containing the same number of elements as in the original array, but the original elements have been altered in some way after being passed through the code block as arguments. In other words, #map changes the original array in a one-to-one transformation — the array is the same length, but each element is transformed.

numbers = [10, 20, 30]
numbers.map do |n|
n * 10
end

We start with an array of numbers. Using #map to iterate over the array, each element in the array is passed through the code block as an argument. So upon each iteration, this code first runs 10 * 10, then 20 * 10, and then 30 * 10. #map collects the outputs of each iteration and places them in a new array:

=> [100, 200, 300]

The original array had 3 numbers, and so does the output array. Let’s try out another example.

fruits = ["apple", "banana", "strawberry"]
fruits.map do |fruit|
fruit.upcase
end

Here, I want #map to return an array of the same elements in the original array but return them all capitalized.

=> ["APPLE", "BANANA", "STRAWBERRY"]

#find

To me, #find is much easier to understand based simply on its name. The Ruby docs indicate that this method assesses the original array to…wait for it….find the very first instance for which the code block is not false. In other words (and to shed the double negative), it returns the first array element that proves to be true after having been run through the code block. Here’s an example:

numbers = [1, 2, 3, 4, 5]
numbers.find do |n|
n.even?
end
=> 2

Here’s what’s going on: Starting with an array of numbers, I want to find the first number that is an even number. #find searches through the numbers array one element at a time to determine which is the first element that makes the code block true. It returns “2” and stops there because #find only returns the very first occurrence of what we have asked it to find.

Another example:

fruits = ["apple", "banana", "orange", "raspberry", "strawberry"]
fruits.find do |fruit|
fruit.include?("berry")
end

Here, I want #find to return the first element that has “berry” in its name (notice that the array contains two). It starts with “apple” and moves on since it is false. “Banana”, too, is tossed because it does not contain “berry”. Once it reaches “raspberry”, however, the code block proves true. The iterator scoops up that array element, and we get the following:

=> "raspberry"

Because #find returns only the first instance, our result makes sense.

#select

Finally, in this brief but hopefully meaningful tour of enumerables, we have #select. To a beginner (a.k.a. me), #select and #find was another set of enumerables that I could not keep straight. The names sounded very similar, after all. My light bulb moment came when I learned that #select and #find_all are synonymous.

I just discussed #find, which finds and returns only the first, singular instance that satisfies the code block. #select returns all instances that satisfy the code block. Using the example above but swapping in #select:

numbers = [1, 2, 3, 4, 5]
numbers.select do |n|
n.even?
end
=> [2, 4]

And, using the second example from above:

fruits = ["apple", "banana", "orange", "raspberry", "strawberry"]
fruits.select do |fruit|
fruit.include?("berry")
end

I want to get a list of the fruits that contain “berry” in their names. Just like above with #find, the #select method iterates over the fruits array to see whether any of the array elements include “berry”. This time, because #select picks up all array elements for which the code block is true, the resulting array includes both “raspberry” and “strawberry”:

=> ["raspberry", "strawberry"]

I could have used #find_all instead of #select and would have gotten the same result.

In Conclusion

I never thought I would be saying this, but it turns out that enumerables are really interesting and can do some pretty cool things. While this article covered only some basic enumerables, there are plenty of others to get to know and master.

--

--