Ruby’s Enumerable
module adds magical methods
to classes like Array
and Hash
.
In this post, we will learn to implement enumeration with an example.
Enumeration
Enumeration is a process of traversing over objects one by one.
To make a class enumerable, we can include Enumerable
module.
Thus, giving access to methods like #map
, #filter
, #count
, #include?
, #any?
, #uniq
and a lot more!
Check the full list here.
#each
Enumerable module mainly relies on #each
method on the implementing class.
The #each
method is implemented such that when a block is passed,
the contents of the collection are evaluated.
> [1, 2, 3].each { |a| a }
=> [1, 2, 3]
Enumerator
Enumerator
is a class which allows manual iteration over an enumerator object.
When #each
is called without a block, it returns a new Enumerator
object
hence allowing to chain multiple enumerators.
> enumerator = %w[foo bar baz].each
> enumerator
=> #<Enumerator: ["foo", "bar", "baz"]:each>
> enumerator.map.with_index { |w, i| "#{i}:#{w}" }
=> ["0:foo", "1:bar", "2:baz"]
Example
Let us create a linked list in ruby.
Class Node
represents single item in the linked list,
this will hold reference/link to the next item in the list.
class Node
attr_accessor :value, :next
def initialize(value)
@value = value
end
def inspect
{ @value => @next }
end
end
Class LinkedList
will hold reference to the first/head item in the list.
This class will also be responsible for manipulating the list.
(For this example we are simply going to use it for inserting values in the list)
class LinkedList
def <<(value)
if @head
tail.next = Node.new(value)
else
@head = Node.new(value)
end
end
def tail
item = @head
return item if item.next.nil?
return item if item.next.nil? while (item = item.next)
end
def inspect
[@head].inspect
end
end
Awesome! Now we can create linked list
> list = LinkedList.new
> list << 10
> list << 20
> list << 30
> puts list.inspect
=> [{ 10 => { 20 => { 30 => nil } } }]
Making LinkedList Enumerable
Include Enumerable
module and add #each
method in the LinkedList
class.
class LinkedList
include Enumerable
.
.
.
def each(&block)
item = @head
block.call(item.value)
block.call(item.value) while (item = item.next)
end
end
Done! Let’s try a few methods from Enumerable module
> list = LinkedList.new
> list << 30
> list << 20
> list << 10
> list.sort # => [10, 20, 30]
> list.count # => 3
> list.include? 10 # => true
It works!😎 Let’s try a few more.
> list.minmax # => [10, 30]
> list.sum # => 60
> list << 20
> list.uniq # => [30, 20, 10]
> list.filter { |value| value < 25 }
# => [20, 10, 20]
> list << 51
> list.select(&:odd?) # => [51]
Next, when the block is not passed to each, we should return an enumerator object so that the enumerators can be chained.
> list.each
# => NoMethodError (undefined method `call' for nil:NilClass)
To do this, change the #each
method as following
def each(&block)
if block_given?
item = @head
block.call(item.value)
block.call(item.value) while (item = item.next)
else
to_enum(:each)
end
end
Now, we should be able to chain enumerator methods
> list = LinkedList.new
> list << 10
> list << 20
> list << 30
> list.each
=> #<Enumerator: [{10=>{20=>{30=>nil}}}]:each>
> list.each.with_index.to_h
=> { 10=>0, 20=>1, 30=>2 }
Finally,
In this post,
we saw how we can add magical Enumerable
methods to
classes with just include
and an #each
method.
Here’s final code for both the classes: