Skip to content

Ruby Iterators

Iterators are methods that repeatedly invoke a block of code for each element in a collection, such as an Array or Hash. They are a cornerstone of Ruby’s expressive and concise style for working with collections.

Ruby’s Enumerable module, mixed into classes like Array and Hash, provides a rich set of iterator methods. We’ll focus on each and map (also known as collect), and briefly touch upon others.

The each iterator is one of the most fundamental. It executes a given block once for each element in the collection, passing the element as an argument to the block. each always returns the original collection it was called on.

collection.each do |variable|
# code to execute for each element
end
# Or for single-line blocks:
collection.each { |variable| # code }
#!/usr/bin/env ruby
numbers = [1, 2, 3, 4, 5]
puts "Iterating through numbers:"
numbers.each do |number|
puts "Current number: #{number}"
end
# The return value of each is the original array
returned_value = numbers.each { |n| n * 2 } # The block's result is discarded by each
puts "Returned value from 'each': #{returned_value.inspect}" #=> [1, 2, 3, 4, 5]

This will produce the following result:

Iterating through numbers:
Current number: 1
Current number: 2
Current number: 3
Current number: 4
Current number: 5
Returned value from 'each': [1, 2, 3, 4, 5]
#!/usr/bin/env ruby
capitals = { "USA" => "Washington D.C.", "France" => "Paris", "Japan" => "Tokyo" }
capitals.each do |country, capital|
puts "The capital of #{country} is #{capital}."
end
# For hashes, each_pair is an alias for each.
# You can also iterate over just keys or values:
capitals.each_key { |country| puts "Country: #{country}" }
capitals.each_value { |capital_city| puts "Capital: #{capital_city}" }

This will produce:

The capital of USA is Washington D.C.
The capital of France is Paris.
The capital of Japan is Tokyo.
Country: USA
Country: France
Country: Japan
Capital: Washington D.C.
Capital: Paris
Capital: Tokyo

The map iterator (aliased as collect) transforms a collection. It iterates over each element, executes the block, and builds a new array containing the return values of the block for each element.

new_array = collection.map do |variable|
# code whose return value will be part of the new array
end
# Or for single-line blocks:
new_array = collection.map { |variable| # expression }
#!/usr/bin/env ruby
original_numbers = [1, 2, 3, 4, 5]
# Create a new array with each number squared
squared_numbers = original_numbers.map do |n|
n * n
end
puts "Original numbers: #{original_numbers.inspect}"
puts "Squared numbers: #{squared_numbers.inspect}"
# Using the alias 'collect'
uppercased_fruits = ["apple", "banana", "cherry"].collect { |fruit| fruit.upcase }
puts "Uppercased fruits: #{uppercased_fruits.inspect}"

This will produce:

Original numbers: [1, 2, 3, 4, 5]
Squared numbers: [1, 4, 9, 16, 25]
Uppercased fruits: ["APPLE", "BANANA", "CHERRY"]

Note: While collect is a valid alias, map is generally preferred in modern Ruby code for clarity and consistency.

When map is used on a Hash, the block receives [key, value] pairs as arguments. It returns a new array.

#!/usr/bin/env ruby
scores = { "Alice" => 90, "Bob" => 75, "Charlie" => 82 }
descriptions = scores.map do |name, score|
"#{name} scored #{score} points."
end
puts descriptions.inspect
# If you want to transform a hash into another hash, use `transform_values` (Ruby 2.4+) or `map` followed by `to_h`.
incremented_scores = scores.transform_values { |score| score + 5 }
puts "Incremented scores (Hash): #{incremented_scores.inspect}"
# Using map and to_h for more complex transformations resulting in a Hash
student_data = scores.map do |name, score|
[name.to_sym, { score: score, status: score >= 80 ? "Pass" : "Fail" }]
end.to_h
puts "Student data (Hash): #{student_data.inspect}"

This produces:

["Alice scored 90 points.", "Bob scored 75 points.", "Charlie scored 82 points."]
Incremented scores (Hash): {"Alice"=>95, "Bob"=>80, "Charlie"=>87}
Student data (Hash): {:Alice=>{:score=>90, :status=>"Pass"}, :Bob=>{:score=>75, :status=>"Fail"}, :Charlie=>{:score=>82, :status=>"Pass"}}
  • select (or find_all): Returns a new array containing all elements for which the block returns a true value.
  • reject: The opposite of select. Returns a new array containing all elements for which the block returns a false or nil value.
  • find (or detect): Returns the first element for which the block returns a true value.
  • reduce (or inject): Combines all elements of the collection by applying a binary operation, specified by a block or a symbol. It’s powerful for summing numbers, accumulating values, etc.
  • any?, all?, none?: Check if any, all, or no elements satisfy a condition given by a block.
#!/usr/bin/env ruby
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = numbers.select { |n| n.even? }
puts "Even numbers: #{even_numbers.inspect}" #=> [2, 4, 6]
sum = numbers.reduce(0) { |accumulator, current_element| accumulator + current_element }
# Alternative: numbers.reduce(:+) works for simple sums
puts "Sum of numbers: #{sum}" #=> 21
puts "Are there any numbers > 5? #{numbers.any? { |n| n > 5 }}" #=> true

Iterators are used everywhere in Ruby. For example, processing user input, transforming data from a database or API, filtering lists, and generating reports. Chaining iterators allows for elegant and readable data manipulation pipelines.

data = [{id: 1, name: "Alice", active: true}, {id: 2, name: "Bob", active: false}, {id: 3, name: "Charlie", active: true}]
# Get names of active users, uppercased
active_user_names = data.select { |user| user[:active] }
.map { |user| user[:name].upcase }
puts "Active user names: #{active_user_names.inspect}" #=> ["ALICE", "CHARLIE"]

For more information, refer to the Ruby documentation for the Enumerable module: https://ruby-doc.org/core/Enumerable.html