Ruby Enumerable Module
Among the most essential and potent modules in Ruby is the Enumerable module. As a mixin, it gives any class that uses it a wide range of practical iteration and collection manipulation techniques. The Enumerable module in Ruby provides a set of methods for traversing, searching, and manipulating collections of objects. It is a mixin module, meaning it is intended to be included in other classes, imparting its functionality to those classes.
Core Functionality and Implementation
Arrays and hashes are examples of collections that may be traversed, sorted, searched, classified, and transformed using the Enumerable module.
The Requirement of Enumerable Module
The amazing characteristic of the Enumerable module is that it provides about 22 features related to iteration with little effort.
A class that wants to use the Enumerable module’s rich features needs follow one set of rules: it must contain Enumerable and explicitly implement the each instance method.
Every method provided by the host class is the foundation upon which the Enumerable module’s methods such as detect and inject are created. The corresponding block must receive successive members of the collection from each procedure.
Example of Implementing Enumerable
Methods like reduce, select, and map are automatically added to a custom class that implements each and contains Enumerable.
This is an example of code for a custom class that has Enumerable methods:
class NaturalNumbers
include Enumerable # 1. Include the Enumerable module
def initialize(upper_limit)
@upper_limit = upper_limit
end
# 2. Implement the 'each' method
def each(&block)
# Natural numbers typically start at 1, but 0.upto(6) returns 0, 1, 2, 3, 4, 5, 6
# If you meant 1, you should use 1.upto(@upper_limit).
# Assuming you want 0 through @upper_limit based on your 'each' implementation:
0.upto(@upper_limit).each(&block)
end
end
n = NaturalNumbers.new(6)
# The collection is [0, 1, 2, 3, 4, 5, 6]
# Now 'n' can use Enumerable methods:
# n.reduce(:+)
# Sum of 0 + 1 + 2 + 3 + 4 + 5 + 6 = 21
puts n.reduce(:+) # => 21
# n.select(&:even?)
# Selects numbers that are divisible by 2: 0, 2, 4, 6
puts n.select(&:even?).inspect # => [0, 2, 4, 6]
# n.map { |number| number ** 2 }
# Squares each number: 0**2, 1**2, 2**2, 3**2, 4**2, 5**2, 6**2
# 0, 1, 4, 9, 16, 25, 36
puts n.map { |number| number ** 2 }.inspect # => [0, 1, 4, 9, 16, 25, 36]
Output
21
[0, 2, 4, 6]
[0, 1, 4, 9, 16, 25, 36]
Likewise, by defining each iterator and adding Enumerable, the Sequence class that we covered in our earlier discussion is made an enumerable class:
class Sequence
# This is an enumerable class; it defines an each iterator below.
include Enumerable
# 1. ADDED: Initialize method to set the range and step
def initialize(from, to, by)
@from = from
@to = to
@by = by
end
# This is the iterator required by the Enumerable module
def each
x = @from
while x <= @to
yield x
x += @by
end
end
end
# 2. ADDED: Instantiate the class and call an Enumerable method to produce output
# Example 1: Create a sequence from 10 to 30, stepping by 5
s1 = Sequence.new(10, 30, 5)
# Use 'map' (an Enumerable method) to collect the sequence values
puts "Sequence 1 (10 to 30, step 5):"
puts s1.map { |x| x }.inspect # => [10, 15, 20, 25, 30]
# Example 2: Find the count of elements greater than 15
s2 = Sequence.new(10, 50, 8)
puts "\nSequence 2 (10 to 50, step 8):"
puts "Elements: " + s2.map { |x| x }.inspect
puts "Count of elements > 15: " + s2.count { |x| x > 15 }.to_s
# The sequence is [10, 18, 26, 34, 42, 50]
# Count of elements > 15 is 5 (18, 26, 34, 42, 50)
Output
Sequence 1 (10 to 30, step 5):
[10, 15, 20, 25, 30]
Sequence 2 (10 to 50, step 8):
Elements: [10, 18, 26, 34, 42, 50]
Count of elements > 15: 5
Classes that Include Enumerable
Within the Enumerable module, a large number of standard Ruby classes often called collection classes mix. Famous instances consist of:
- Array
- Hash
- Range
- IO
- Set (from the standard library)
- Dir
- String (while generally String methods are not dependent on each, they can generate enumerators in some situations)
- Struct
Key Methods Provided by Enumerable
There are many methods defined by the Enumerable module. Several of the most popular techniques are sort_by, find_all, inject, and collect.
Each and its Variants
In the fundamental iterator, every element of the collection is passed to the corresponding block.
Method | Description | Code Example | Source |
each | Iterates over every element of a collection. | `.each { | x |
each_with_index | Yields the element and an integer index/line number. | `(5..7).each_with_index { | x,i |
Transformation and Selection
Based on the outcomes, these methods return a new array after executing the related block.
Method | Description | Code Example | Result | Source |
collect (or map ) | Executes the block for each element and collects the block’s return values into a new array. | `.collect { | x | x*x}` |
select (or find_all ) | Returns an array of elements for which the block returns a value that is neither false nor nil . | `(1..10).select { | x | x%2 == 0}` |
reject | Returns an array of elements for which the block returns nil or false . | `(1..10).reject { | x | x%2 == 0}` |
Accumulation ()
A value is accumulated over iterations by the inject method, commonly referred to as reduce. The accumulated value (sum or memo) and the current collection element are the two arguments used to call the block. For the subsequent iteration, the block’s return value serves as the accumulator.
Method | Description | Code Example | Result | Source |
inject | Combines the elements by applying the block to an accumulator and each element. | data = `sum = data.inject { | sum, x | sum + x }` |
Searching and Testing
Method | Description | Code Example | Source |
detect (or find ) | Returns the first element for which the block returns true. | (Implicit in source listing) | |
all? | Returns true if the block returns true for all elements. | (Implicit in source listing) | |
any? | Returns true if the block returns true for any element. | (Implicit in source listing) |
Enumerable and Enumerators
Many standard iterators and Enumerable methods in Ruby 1.9 return an Enumerator object when they are called without a corresponding code block. Enumerable objects are enumerator objects.
Method chaining is made possible by this behavior, allowing operations to be functionally composed.
Code Example (Returning an Enumerator):
# The `downto` iterator returns an Enumerator if no block is supplied
enumerator = 10.downto(1)
# puts enumerator.inspect # => #<Enumerator: 10:downto(1)>
# The Enumerator itself can then use Enumerable methods like select
# ADDED: The 'puts' command to display the result
puts enumerator.select {|x| x%2==0}.inspect
Output
[10, 8, 6, 4, 2]
When no block is supplied, an iteration method such as each is created to return an Enumerator, allowing for intricate chained operations:
class MyIterator
# The iterator method that yields symbols
def each
# This is the key line: returns an Enumerator if no block is supplied
return enum_for :each unless block_given?
yield :x
yield :y
yield :z
end
end
# 1. Instantiate the class
iterator = MyIterator.new
# 2. Call the chain of Enumerator/Enumerable methods and use 'puts' to display the result
puts iterator.each.drop(2).map(&:upcase).first
Output
Z
You can also read Exception In Ruby: Custom Classes And The Hierarchy