CrackedRuby logo

CrackedRuby

each and Enumerator Methods

Overview

Ruby's each method and Enumerator class form the foundation of iteration in Ruby. The each method traverses collections element by element, executing a block for each item. Every enumerable object in Ruby implements each, making it the base method for iteration patterns across arrays, hashes, ranges, and custom collections.

The Enumerator class creates iterator objects that encapsulate iteration state. Ruby automatically returns an Enumerator when enumerable methods are called without blocks. This enables method chaining, lazy evaluation, and external iteration control.

# Basic each iteration
[1, 2, 3].each { |n| puts n }
# => 1
# => 2  
# => 3

# Enumerator creation
enum = [1, 2, 3].each
enum.class
# => Enumerator

# External iteration
enum.next
# => 1
enum.next  
# => 2

Ruby implements each differently across collection types. Arrays iterate by index, hashes yield key-value pairs, and ranges generate sequential values. The Enumerator class provides consistent external iteration regardless of the underlying collection type.

# Hash iteration yields key-value pairs
{a: 1, b: 2}.each { |key, value| puts "#{key}: #{value}" }
# => a: 1
# => b: 2

# Range iteration generates values
(1..3).each { |n| puts n * 2 }
# => 2
# => 4
# => 6

Basic Usage

The each method accepts a block and executes it for every element in the collection. The method always returns the original object, not the block results. This behavior enables method chaining while maintaining the receiver object.

# Array iteration
numbers = [10, 20, 30]
result = numbers.each { |n| n * 2 }
result.equal?(numbers)
# => true

# Block parameters receive collection elements
words = ['ruby', 'python', 'javascript']
words.each { |word| puts word.upcase }
# => RUBY
# => PYTHON  
# => JAVASCRIPT

Hash iteration passes both key and value to the block as separate parameters. Single parameter blocks receive two-element arrays containing key-value pairs.

# Two-parameter block
data = {name: 'Alice', age: 30, city: 'Boston'}
data.each { |key, value| puts "#{key} is #{value}" }
# => name is Alice
# => age is 30
# => city is Boston

# Single parameter block receives arrays
data.each { |pair| puts pair.inspect }
# => [:name, "Alice"]
# => [:age, 30]
# => [:city, "Boston"]

Calling enumerable methods without blocks returns Enumerator objects. These enumerators support external iteration through next and peek methods.

# Creating enumerators
array_enum = [1, 2, 3].each
hash_enum = {a: 1, b: 2}.each_with_index

# External iteration
array_enum.next
# => 1
array_enum.peek  
# => 2
array_enum.next
# => 2

# Enumerators are rewindable
array_enum.rewind
array_enum.next
# => 1

Enumerators enable method chaining on collections that don't normally support enumerable methods. The with_index method adds indices to any enumerator.

# Adding indices to iteration
['a', 'b', 'c'].each.with_index { |char, i| puts "#{i}: #{char}" }
# => 0: a
# => 1: b
# => 2: c

# Chaining with other enumerable methods
[1, 2, 3, 4].each.with_index.select { |num, idx| idx.even? }
# => [[1, 0], [3, 2]]

Advanced Usage

Custom enumerators extend iteration patterns beyond built-in collections. The Enumerator constructor accepts objects and method names, or blocks that yield values.

# Custom enumerator from object and method
class Counter
  def initialize(limit)
    @limit = limit
  end
  
  def count_up
    (1..@limit).each { |n| yield n }
  end
end

counter = Counter.new(3)
enum = Enumerator.new(counter, :count_up)
enum.each { |n| puts n * 10 }
# => 10
# => 20
# => 30

Block-based enumerators use the yielder object to generate values dynamically. This pattern creates infinite or computed sequences.

# Fibonacci sequence enumerator
fibonacci = Enumerator.new do |yielder|
  a, b = 0, 1
  loop do
    yielder << a
    a, b = b, a + b
  end
end

fibonacci.take(8)
# => [0, 1, 1, 2, 3, 5, 8, 13]

# Prime number generator
primes = Enumerator.new do |yielder|
  candidates = (2..Float::INFINITY).lazy
  candidates.each do |num|
    is_prime = (2...num).none? { |i| num % i == 0 }
    yielder << num if is_prime
  end
end

primes.take(5)
# => [2, 3, 5, 7, 11]

Lazy enumerators defer computation until values are requested. The lazy method creates lazy enumerators that optimize memory usage for large datasets.

# Lazy evaluation avoids intermediate arrays
(1..1_000_000).lazy.select(&:even?).map(&:to_s).take(3)
# => ["2", "4", "6"]

# Without lazy, this creates large intermediate collections
# (1..1_000_000).select(&:even?).map(&:to_s).take(3)
# Creates array of 500,000 integers, then 500,000 strings

# Combining lazy with custom enumerators
fibonacci.lazy.select(&:even?).take(5)
# => [0, 2, 8, 34, 144]

Enumerator composition builds complex iteration patterns from simple components. Multiple enumerators can be chained, zipped, or combined.

# Parallel iteration with zip
letters = ['a', 'b', 'c'].each
numbers = [1, 2, 3].each
letters.zip(numbers) { |letter, num| puts "#{letter}#{num}" }
# => a1
# => b2  
# => c3

# Custom enumerable class
class TodoList
  include Enumerable
  
  def initialize
    @items = []
  end
  
  def add(item, priority: :normal)
    @items << {task: item, priority: priority}
  end
  
  def each
    return enum_for(:each) unless block_given?
    @items.sort_by { |item| priority_order(item[:priority]) }.each do |item|
      yield item
    end
  end
  
  private
  
  def priority_order(priority)
    {high: 1, normal: 2, low: 3}[priority]
  end
end

todos = TodoList.new
todos.add("Buy groceries", priority: :low)
todos.add("Fix bug", priority: :high)
todos.add("Write docs", priority: :normal)

todos.map { |item| item[:task] }
# => ["Fix bug", "Write docs", "Buy groceries"]

Common Pitfalls

The return value of each frequently confuses developers. Unlike map or select, each always returns the original receiver object, not the block results.

# Common mistake: expecting block results
numbers = [1, 2, 3]
doubled = numbers.each { |n| n * 2 }
doubled
# => [1, 2, 3]  # Original array, not [2, 4, 6]

# Correct approach for transformation
doubled = numbers.map { |n| n * 2 }
# => [2, 4, 6]

# Using each for side effects only
numbers.each { |n| puts n * 2 }  # Prints doubled values
# => [1, 2, 3]  # Still returns original array

Enumerator state persists across calls. Calling next multiple times advances the internal position, and reaching the end raises StopIteration exceptions.

# Enumerator exhaustion
enum = [1, 2].each
enum.next  # => 1
enum.next  # => 2
enum.next  # => StopIteration: iteration reached an end

# Safe iteration with rewind
enum.rewind
enum.next  # => 1

# Using enumerators in loops
begin
  loop { puts enum.next }
rescue StopIteration
  puts "Iteration complete"
end

Modifying collections during iteration causes unpredictable behavior. Ruby's each implementation may skip elements or iterate over newly added items.

# Dangerous: modifying during iteration
numbers = [1, 2, 3, 4, 5]
numbers.each do |n|
  numbers.delete(n) if n.even?  # Unpredictable results
end
numbers  
# => [1, 3, 5] or [1, 3, 4, 5] depending on Ruby version

# Safe approach: iterate over copy
numbers = [1, 2, 3, 4, 5]
numbers.dup.each do |n|
  numbers.delete(n) if n.even?
end
numbers
# => [1, 3, 5]  # Predictable result

# Alternative: collect indices first
to_remove = []
numbers.each_with_index do |n, i|
  to_remove << i if n.even?
end
to_remove.reverse.each { |i| numbers.delete_at(i) }

Lazy enumerators defer computation but maintain references to source objects. This prevents garbage collection and can cause memory leaks with large datasets.

# Memory leak potential
def create_large_enum
  large_array = (1..1_000_000).to_a
  large_array.lazy.select(&:even?)  # Retains reference to large_array
end

enum = create_large_enum
# large_array cannot be garbage collected while enum exists

# Better approach: process immediately
def process_large_data
  (1..1_000_000).lazy.select(&:even?).take(100).to_a
end
# No persistent reference to intermediate data

Reference

Core Each Methods

Method Parameters Returns Description
#each &block self Executes block for each element
#each_with_index &block self or Enumerator Yields element and index
#each_with_object(obj) obj, &block obj Yields element and accumulator object
#reverse_each &block self or Enumerator Iterates in reverse order

Enumerator Class Methods

Method Parameters Returns Description
Enumerator.new(obj, method) Object, Symbol Enumerator Creates enumerator from object method
Enumerator.new(&block) &block Enumerator Creates enumerator from block
#next None Object Returns next value, advances position
#peek None Object Returns next value without advancing
#rewind None self Resets iteration to beginning
#with_index(offset=0) Integer Enumerator Adds index to yielded values
#with_object(obj) Object Enumerator Yields values with accumulator object
#size None Integer or nil Returns iteration count if known
#lazy None Enumerator::Lazy Creates lazy enumerator

Collection-Specific Each Behavior

Collection Type Block Parameters Iteration Order Notes
Array element Index order (0, 1, 2...) Most common usage
Hash key, value Insertion order (Ruby 1.9+) Single param gets [key, value] array
Range element Sequential Generates values dynamically
String character Character order Requires #each_char
Set element Unspecified No guaranteed order
Struct value Definition order Iterates over values only

Exception Hierarchy

StandardError
└── StopIteration
    ├── message (String) - Description of termination
    └── result - Final value from iteration

Enumerator State Methods

Method Description Usage Pattern
#feed(value) Provides value to yielding block Advanced generator patterns
#next_values Returns array of all yielded values Multiple yield per iteration
#peek_values Peeks at array of yielded values Multiple yield inspection

Performance Characteristics

Operation Time Complexity Memory Usage Notes
Array#each O(n) O(1) additional Direct index access
Hash#each O(n) O(1) additional Bucket traversal
Range#each O(n) O(1) additional Computed values
Enumerator#next O(1) O(1) per state Maintains iteration state
Lazy#each O(1) setup Deferred Computation on demand

Common Enumerator Patterns

# Infinite sequences
naturals = (1..Float::INFINITY).each

# Cycling through values  
colors = %w[red green blue].cycle

# Repeating single value
zeros = Enumerator.new { |y| loop { y << 0 } }

# File line iteration
file_enum = File.foreach('data.txt')

# Custom object enumeration
class_enum = Enumerator.new(MyClass.new, :iterate_method)