The Enumerable Module

Digging into the useful and misunderstood module which gives us so many yummy iterators and more.

Scroll down...

Content

Resources

Comments

You've learned about Arrays and Hashes but only got half the story...

Each of these two classes has its own methods for adding and deleting and accessing data and they each implement their own slightly different version of the each method to iterate over their items. Despite these (minor) differences, what makes them really powerful in Ruby is their ability to use Enumerable methods as well as the basic ones you've learned about in previous lessons.

In this lesson, we'll dig into what Enumerable actually is and learn more about the most popular methods it brings (like map and select). Our goal is to remove any remaining mystique surrounding what these methods do and to empower you to use them freely.

The Enumerable Module

"Enumerable" is actually a "module", which means it is just a bunch of methods packaged together that can (and do) get "mixed in", or included, with other classes like Array and Hash. It's designed to provide useful functionality to classes representing collections of objects.

Modules like Enumerable are the ultimate in DRY code! Ruby programmers don't have to write all those methods many different times - they just write them once, package them up as Enumerable, and tell Array and Hash to include them.

Prerequisite: Each

Enumerable gives you lots of useful ways of working with the elements of a collection object like an array or hash. But Enumerable is a module so it isn't just limited to arrays and hashes. You can include it in anything you want, subject to one major caveat:

Because every collection is different, Enumerable needs that collection to have its own implementation of the each method. Otherwise it wouldn't know how to iterate across each element of the collection.

each is an iterator method you've seen plenty of times before now, including in the lesson Blocks, Procs and Lambdas. For quick review, it comes pre-packaged with the Array and Hash and Range classes and it basically just goes through each item in the object you called it on and passes that item to the block you specified.

It will also return the original collection that it was called on:

> [1,2,3].each { |num| print "#{num}! " }
1! 2! 3! #=> [1,2,3]

Each With Index

We already looked at each_with_index briefly in the lesson Iteration but it should be interesting to understand it in the context of Enumerable. each_with_index will pass the current item's position into the block as well as the item itself:

> ["Cool", "chicken!", "beans!", "beef!"].each_with_index do |item, index|
>   print "#{item} " if index.even?
> end
Cool beans! => ["Cool", "chicken!", "beans!", "beef!"]

Select

What if I want to keep only the even numbers that are in an array? The traditional way would be to build some sort of loop that goes through the array, checks whether each element is even, and starts populating a temporary array that we will return at the end.

That might look something like:

> class Array
>   def keep_evens
>     result_array = []
>     i = 0
>     while i < self.length
>       num = self[i]
>       result_array << num if num.even?
>       i += 1
>     end
>     return result_array
>   end
> end
#=> :keep_evens
> my_array = [1,2,3,4,5,6,7,8,100]
> my_array.keep_evens
#=> [2,4,6,8,100]

That's too much code and too much hassle. When all you're doing is pulling out, or selecting, certain items based on some criteria, you'd be better served using Enumerable's select instead.

select will run the block on every item of your object (whether that object is an array or hash or whatever) and return a new object that contains only those items for which the original block returned true:

> my_array.select{|item| item.even? }
#=> [2,4,6,8,100]      # wow, that was easy.

You win this round, Ruby. Also note that we're actually using the implicit return of the block to help us decide whether to include each item in the final returned array.

Map (aka Collect)

What if instead of selecting just a few items we want to keep ALL items but modify them somehow?

That sounds a lot like we're doing something and collecting the results, doesn't it? collect will run your block and give you an object filled with whatever your block returned each time. Ruby says:

> my_array.collect{|num| num**2 }
#=> [4,16,36,64,10000]

You've heard of map? It's the EXACT same method as collect, just called something different.

Because Ruby likes to confuse you sometimes with so many options.

Some people visualize it in their heads as doing something and collecting the results, other people see it as re-mapping your original object through some sort of transformation. It's more conventional to use map but both work the same way.

Here's a theoretical example more like what you might see when you've got your own website built using Rails, where we may want to send only an array filled with our users' emails out to the webpage:

u = User.all
@user_emails = u.map { |user| user.email }

Enumerable on Hashes

We've mostly used arrays in our examples because they're simple and common. But of course you can also use the enumerable methods on hashes as well. Just remember that each for a hash passes TWO inputs to the block, one for the current key and one for the current value, so enumerable methods will take the same format:

> my_hash = {"Joe" => "male", "Jim" => "male", "Patty" => "female"}
> my_hash.select{|name, gender| gender == "male" }
#=> {"Joe" => "male", "Jim" => "male"}

Common Enumerable Iterators Review

  • each iterates through each item of the collection and returns the original object it was called on because it's really used for its side effects. Used by Enumerable methods.
  • each_with_index passes in not just the current item but whatever position in the array it was located in. It also returns the original array.
  • select returns a new object (e.g. array) filled with only those original items where the block you gave it returned true.
  • map returns a new array filled with whatever gets returned by the block each time it runs.

Inject (aka Reduce)

Up until now, all the methods we've seen run essentially independent operations on each element of our array or hash. What if we want to do something that keeps track of the result as we iterate? Like, say, summing up the elements of an array?

For that we need to use inject (aka reduce), which passes not just the element but whatever was returned by the previous iteration into the block. You can either specify the initial value or it will just default to the first item of the array. It ultimately returns whatever the result of the last iteration is. Here's a way to sum an array:

> my_array.inject(0){|running_total, item| running_total + item }
#=> 120

We're including this for completeness but you'll rarely use it.

Why is it called reduce? Because it reduces your entire collection to one value.

Other Useful Enumerable Methods

Enumerable contains a very large group of methods and you'll only use a half-dozen of them regularly (e.g. map and select) but there are some others that you should be familiar with as well. The full list is available in the docs here.

  • any? returns true/false (see the question mark?) and answers the question, "do ANY of the elements in this object pass the test in my block?". If your block returns true on any time it runs, any? will return true.
  • all? returns true/false and answers the question, "do ALL the elements of this object pass the test in my block?". Every time the block runs it must return true for this method to return true.
  • none? returns true only if NONE of the elements in the object return true when the block is run.
  • find returns the first item in your object for which the block returns true.
> [1,4,6,9].any?{ |item| item.odd? }
#=> true
> [1,4,6,9].all?{ |item| item.odd? }
#=> false
> [1,4,6,9].none?{ |item| item > 8 }
#=> false   # 9 is greater than 8 so it's false
> [1,4,6,9].find{ |item| item > 5 }
#=> 6

Awesome but less common methods

...included only for completeness, so don't go out and practice them unless you've got time to spare.

  • group_by will run your block and return a hash that groups all the different types of returns from that block.
  • grep returns an array with those items that actually match the specified criteria (RegEx) (using a === match which, if you recall, checks whether the argument is a type of the object).
> names = ["James", "Bob", "Joe", "Mark", "Jim"]
> names.group_by{|name| name.length}
#=> {5=>["James"], 3=>["Bob", "Joe", "Jim"], 4=>["Mark"]} 
> names.grep(/J/)
#=> ["James", "Joe", "Jim"]

Familiar Friends

Some of the methods you've already seen and use are part of Enumerable too -- include?, sort, and count. Group hug!

Enumerators

When you use the Enumerable methods, you'll sometimes see what's called an enumerator object pop up, usually if you forget to give the Enumerable method a parameter that it wants (like a block).

> [1,4,6,9].find
#=> #<Enumerator: [1, 4, 6, 9]:find> 

What the heck is that?

Consider it an implementation detail of Enumerator. As we said before, the methods that are part of Enumerable rely on the underlying collections' each method to work.

Enumerator is basically a go-between for the original collection and Enumerable which is designed to standardize every collection into something that Enumerable can use. You can tell an Enumerator object to give you the next or previous item in the collection, for instance.

We'll poke around Enumerators in a later lesson but they're more useful for helping to understand Enumerable than as a stand-alone resource themselves.



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

Sorry, comments aren't active just yet!

Next Lesson: Pass by Reference