The Ruby Enumerable
module deserves the top spot in the popularity contest. It packs so many great methods that it's hard to think of an application that wouldn't use it.
Since inheritance in Ruby is limited to a single parent class, modules allow you to share functionality between unrelated classes.
Enumerable
is such a module. One that you can mix into your classes.
Let's see for example, what modules are included in the Array
, and Hash
classes.
Array.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]
Hash.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]
Why would you use Enumerable in your class?
The Enumerable
module provides you with methods for searching, traversal, and sorting collections.
So if you're writing something that has to deal with collections, you can just mix in the Enumerable
module, and you're good to go. There's no need for you to rewrite functionality that you can get for free.
How to make your class Enumerable
So, to use the Enumerable
module, you simply include
it in your class, and provide an each
method that yields each value to a block. That's it!
class Person
attr_accessor :name
include Enumerable
def initialize(name)
@name = name
end
def each
yield name
end
end
Person.new("Cezar").to_a # => ["Cezar"]
You can learn more about attr_accessor
if you want.
As you can see in the example above, I don't have to write the to_a
method myself. I can get it for free by including the Enumerable
module.
But let's see something more interesting.
class Person
attr_accessor :emails
include Enumerable
end
me = Person.new
me.emails = ["home@gmail.com", "work@gmail.com", "newsletters@gmail.com"]
me.emails.sort
# => ["home@gmail.com", "newsletters@gmail.com", "work@gmail.com"]
Again, I didn't have to write sort
myself. That too comes from the Enumerable
module.
Enumerable vs. Enumerator what is the difference?
Even though they look very similar, there is a big difference between the two.
The Enumerable
module is the thing you include in your classes to get all the Enumerable
methods.
The Enumerator
is a class that includes the Enumerable
module, just like other classes do. Its purpose is to create enumerable objects that can be chained together.
e = [1, 2, 3].map # => #<Enumerator: ...>
e.each_with_index { |n, i| n * i } # => [0, 2, 6]
Another cool thing you can do is call methods on it. For example if you want to get the next element, you can do this.
e = [1, 2, 3].map # => #<Enumerator: ...>
e.next # => 1
e.next # => 2
Build your own Enumerable module
What better way to learn what the Ruby Enumerable
module does than to create one from scratch.
module Reduceable
def reducer(acc)
each do |value|
acc = yield(acc, value)
end
acc
end
end
class Person
attr_accessor :emails
include Reduceable
def number_of_emails
reducer(0) { |total, email| total + email.size }
end
def each
yield emails
end
end
me = Person.new
me.emails = ["home@gmail.com", "work@gmail.com", "newsletters@gmail.com"]
me.number_of_emails # => 3
The reducer
method receives an initial value as the accumulator (0
in this case), and for each value, it passes both the accumulator and the email to the block it was given. Finally, it returns the accumulator.
You can do anything you want with those arguments inside the block. But this example counts the number of emails present in the emails
array.
How to find the min/max value
That's a perfect example of an everyday use case that Enumerable
provides.
[1, 2, 3].min # => 1
[1, 2, 3].max # => 3
You can also get more than one value back, by passing a count to min
or max
.
[1, 2, 3].min(2) # => [1, 2]
[1, 2, 3].max(2) # => [3, 2]
How to reset an enumerator
A neat trick you can do with an Enumerator
is to rewind
it to its first element.
e = [1, 2, 3].map # => #<Enumerator: ...>
e.next # => 1
e.next # => 2
e.rewind # => #<Enumerator: ...>
e.next # => 1
Reversing an enumerable
There's a reverse_each
method that returns an Enumerator
with the elements reversed.
[1, 2, 3].reverse_each.to_a # => [3, 2, 1]
How to remove duplicates enumerable lists
The method you want is called uniq
.
[ "a", "a", "b", "b", "c" ].uniq # => ["a", "b", "c"]
Count vs. size vs. length
While size
is nothing more than an alias for length
, the difference between length
and count
is that count
can take a block. If you pass a block to count
, it only counts the number of elements for which the block returns truthy.
[1, :a, "b", nil, false].length # => 5
[1, :a, "b", nil, false].count { |n| n } # => 3
In the example above, 1
, :a
, and "a"
are all truthy (the are not nil
or `false). So the result is 3.
Is $<
enumerable?
$<
is just an alias for ARGF
, and it includes the Enumerable
module.
ARGF.class # => ARGF.class
ARGF.class.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]