Ruby Loops
Loops are fundamental control structures in programming used to repeatedly execute a block of code until a specific condition is met. Ruby provides various loop statements and iterators that enable code to efficiently handle repetitive tasks. This chapter will introduce in detail the various loop statements in Ruby and how to use them.
🔄 while Loops
Basic while Loops
ruby
# Basic syntax: while condition do ... end
counter = 0
while counter < 5
puts "Counter: #{counter}"
counter += 1
end
# Output:
# Counter: 0
# Counter: 1
# Counter: 2
# Counter: 3
# Counter: 4Single-Line while Loops
ruby
# Modifier form of while loop
counter = 0
puts "Counter: #{counter += 1}" while counter < 5
# Output:
# Counter: 1
# Counter: 2
# Counter: 3
# Counter: 4
# Counter: 5while Loop Variations
ruby
# Use break to exit loop early
counter = 0
while true
puts "Counter: #{counter}"
counter += 1
break if counter >= 5
end
# Use next to skip current iteration
counter = 0
while counter < 10
counter += 1
next if counter.even? # Skip even numbers
puts "Odd: #{counter}"
end
# Output:
# Odd: 1
# Odd: 3
# Odd: 5
# Odd: 7
# Odd: 9🔁 until Loops
Basic until Loops
ruby
# until is the reverse form of while: until condition do ... end
counter = 0
until counter >= 5
puts "Counter: #{counter}"
counter += 1
end
# Equivalent to: while counter < 5Single-Line until Loops
ruby
# Modifier form of until loop
counter = 0
puts "Counter: #{counter += 1}" until counter >= 5
# Output:
# Counter: 1
# Counter: 2
# Counter: 3
# Counter: 4
# Counter: 5🔁 loop Loops
Basic loop Loops
ruby
# loop creates infinite loop, must use break to exit
counter = 0
loop do
puts "Counter: #{counter}"
counter += 1
break if counter >= 5
endloop Loop Applications
ruby
# Read user input until "quit" is entered
loop do
print "Enter command (enter 'quit' to exit): "
input = gets.chomp
break if input == "quit"
puts "You entered: #{input}"
end
# Retry mechanism
def fetch_data
attempts = 0
max_attempts = 3
loop do
begin
# Simulate network request
if rand > 0.3 # 70% success rate
return "Data fetched successfully"
else
raise "Network error"
end
rescue => e
attempts += 1
if attempts >= max_attempts
puts "Maximum retry attempts reached"
return nil
else
puts "Retrying attempt #{attempts}..."
sleep 1
end
end
end
end🔢 Numeric Loops
times Loops
ruby
# Integer's times method
5.times do |i|
puts "Iteration #{i + 1}"
end
# Output:
# Iteration 1
# Iteration 2
# Iteration 3
# Iteration 4
# Iteration 5
# Single-line form
5.times { |i| puts "Counter: #{i}" }
# Without index parameter
5.times do
puts "Hello!"
endupto and downto Loops
ruby
# From current number to specified number
1.upto(5) do |i|
puts "Counting up: #{i}"
end
# From current number to specified number (downward)
10.downto(5) do |i|
puts "Counting down: #{i}"
end
# Single-line form
1.upto(3) { |i| puts "Number: #{i}" }
10.downto(8) { |i| puts "Countdown: #{i}" }step Loops
ruby
# Loop with specified step
1.step(10, 2) do |i|
puts "Step 2: #{i}"
end
# Output: 1, 3, 5, 7, 9
# Reverse step
10.step(1, -2) do |i|
puts "Reverse step 2: #{i}"
end
# Output: 10, 8, 6, 4, 2📦 Collection Iteration
each Iterator
ruby
# Array iteration
fruits = ["Apple", "Banana", "Orange"]
fruits.each do |fruit|
puts "Fruit: #{fruit}"
end
# Hash iteration
person = {name: "Zhang San", age: 25, city: "Beijing"}
person.each do |key, value|
puts "#{key}: #{value}"
end
# Single-line form
fruits.each { |fruit| puts fruit }
# Iterate only keys or values
person.each_key { |key| puts key }
person.each_value { |value| puts value }each_with_index Iterator
ruby
# Get both element and index simultaneously
fruits = ["Apple", "Banana", "Orange"]
fruits.each_with_index do |fruit, index|
puts "Fruit #{index + 1}: #{fruit}"
end
# Hash with each_with_index
person = {name: "Zhang San", age: 25, city: "Beijing"}
person.each_with_index do |(key, value), index|
puts "Item #{index + 1}: #{key} = #{value}"
endmap/collect Iterator
ruby
# Transform array elements
numbers = [1, 2, 3, 4, 5]
# map returns new array
squared = numbers.map { |n| n * n }
puts squared.inspect # [1, 4, 9, 16, 25]
# In-place modification using map!
numbers.map! { |n| n * 2 }
puts numbers.inspect # [2, 4, 6, 8, 10]
# String processing example
names = ["Zhang San", "Li Si", "Wang Wu"]
capitalized = names.map { |name| name.upcase }
puts capitalized.inspect # ["ZHANG SAN", "LI SI", "WANG WU"]select/reject Iterator
ruby
# Filter elements
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Select even numbers
evens = numbers.select { |n| n.even? }
puts evens.inspect # [2, 4, 6, 8, 10]
# Reject odd numbers (equivalent to selecting even)
evens = numbers.reject { |n| n.odd? }
puts evens.inspect # [2, 4, 6, 8, 10]
# String filtering example
words = ["apple", "banana", "cherry", "date"]
long_words = words.select { |word| word.length > 5 }
puts long_words.inspect # ["banana", "cherry"]find/detect Iterator
ruby
# Find first matching element
numbers = [1, 3, 5, 8, 9, 12]
# Find first even number
first_even = numbers.find { |n| n.even? }
puts first_even # 8
# Find first word longer than 5 characters
words = ["cat", "dog", "elephant", "bird"]
long_word = words.find { |word| word.length > 5 }
puts long_word # elephant
# If not found, returns nil
no_match = numbers.find { |n| n > 20 }
puts no_match # nil
# Provide default value
no_match_with_default = numbers.find(-> { "Not found" }) { |n| n > 20 }
puts no_match_with_default # Not found🔧 Advanced Iterators
reduce/inject Iterator
ruby
# Reduction operations
numbers = [1, 2, 3, 4, 5]
# Calculate sum
sum = numbers.reduce(0) { |acc, n| acc + n }
puts sum # 15
# Shorthand form
sum = numbers.reduce(:+)
puts sum # 15
# Calculate product
product = numbers.reduce(1) { |acc, n| acc * n }
puts product # 120
# Find maximum
max = numbers.reduce { |max, n| n > max ? n : max }
puts max # 5
# String concatenation
words = ["Hello", " ", "World", "!"]
sentence = words.reduce("") { |acc, word| acc + word }
puts sentence # Hello World!group_by Iterator
ruby
# Group by condition
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Group by parity
groups = numbers.group_by { |n| n.even? ? "Even" : "Odd" }
puts groups
# {"Odd"=>[1, 3, 5, 7, 9], "Even"=>[2, 4, 6, 8, 10]}
# Group strings by length
words = ["cat", "dog", "elephant", "bird", "ant"]
by_length = words.group_by { |word| word.length }
puts by_length
# {3=>["cat", "dog"], 8=>["elephant"], 4=>["bird"], 3=>["ant"]}
# Note: Same keys are merged, actual result is {3=>["cat", "dog", "ant"], 8=>["elephant"], 4=>["bird"]}sort_by Iterator
ruby
# Sort by condition
students = [
{name: "Zhang San", age: 20, score: 85},
{name: "Li Si", age: 19, score: 92},
{name: "Wang Wu", age: 21, score: 78}
]
# Sort by score
by_score = students.sort_by { |student| student[:score] }
puts by_score.map { |s| s[:name] } # ["Wang Wu", "Zhang San", "Li Si"]
# Sort by age (descending)
by_age = students.sort_by { |student| -student[:age] }
puts by_age.map { |s| s[:name] } # ["Wang Wu", "Zhang San", "Li Si"]⚙️ Loop Control Statements
break Statement
ruby
# Exit loop early
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Find first number greater than 5
result = numbers.each do |n|
if n > 5
puts "Found first number greater than 5: #{n}"
break n # Return n as the result of each method
end
end
puts "Result: #{result}" # Result: 6next Statement
ruby
# Skip current iteration
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Only process odd numbers
numbers.each do |n|
next if n.even? # Skip even numbers
puts "Odd: #{n}"
end
# Output:
# Odd: 1
# Odd: 3
# Odd: 5
# Odd: 7
# Odd: 9redo Statement
ruby
# Re-execute current iteration
counter = 0
3.times do |i|
counter += 1
puts "Iteration #{i + 1}, Counter: #{counter}"
redo if counter < 3 && i == 1 # Re-execute on second iteration
end
# Output:
# Iteration 1, Counter: 1
# Iteration 2, Counter: 2
# Iteration 2, Counter: 3
# Iteration 3, Counter: 4retry Statement
ruby
# Retry in exception handling
attempts = 0
begin
attempts += 1
puts "Attempt #{attempts}"
# Simulate operation that may fail
if attempts < 3
raise "Operation failed"
end
puts "Operation successful"
rescue => e
puts "Error: #{e.message}"
retry if attempts < 5
puts "Give up retrying"
end🧪 Loop Practice Examples
Data Processing Pipeline
ruby
class DataProcessor
def self.process(data)
data
.select { |item| item[:active] } # Filter active items
.sort_by { |item| -item[:priority] } # Sort by priority
.map { |item| transform_item(item) } # Transform data
.take(10) # Take first 10 items
end
private
def self.transform_item(item)
{
id: item[:id],
name: item[:name].upcase,
score: calculate_score(item),
processed_at: Time.now
}
end
def self.calculate_score(item)
(item[:value] * item[:weight]).round(2)
end
end
# Use data processing pipeline
raw_data = [
{id: 1, name: "Project A", value: 10, weight: 2.5, priority: 1, active: true},
{id: 2, name: "Project B", value: 15, weight: 1.8, priority: 3, active: true},
{id: 3, name: "Project C", value: 8, weight: 3.2, priority: 2, active: false},
{id: 4, name: "Project D", value: 12, weight: 2.1, priority: 4, active: true}
]
processed_data = DataProcessor.process(raw_data)
processed_data.each { |item| puts item }File Processing Loop
ruby
class FileProcessor
def self.process_files(directory, pattern = "*.txt")
processed_count = 0
error_count = 0
Dir.glob(File.join(directory, pattern)).each_with_index do |file_path, index|
begin
puts "Processing file #{index + 1}: #{File.basename(file_path)}"
# Read file content
content = File.read(file_path, encoding: "UTF-8")
# Process content
processed_content = process_content(content)
# Save processing result
output_path = file_path.sub(/\.txt$/, "_processed.txt")
File.write(output_path, processed_content)
processed_count += 1
puts " ✓ Processing complete"
rescue => e
error_count += 1
puts " ✗ Processing failed: #{e.message}"
next # Continue processing next file
end
end
puts "\nProcessing complete: #{processed_count} successful, #{error_count} failed"
end
private
def self.process_content(content)
# Example processing: Convert to uppercase and add timestamp
processed = content.upcase
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
processed + "\n\nProcessing time: #{timestamp}"
end
end
# Use file processor (needs actual files to run)
# FileProcessor.process_files("/path/to/directory")User Input Validation Loop
ruby
class UserInputValidator
def self.get_valid_input(prompt, validation_proc, error_message)
loop do
print prompt
input = gets.chomp
if validation_proc.call(input)
return input
else
puts error_message
end
end
end
def self.get_number_in_range(min, max)
get_valid_input(
"Enter number between #{min} and #{max}: ",
->(input) {
begin
num = Integer(input)
num >= min && num <= max
rescue ArgumentError
false
end
},
"Invalid input, please enter an integer between #{min} and #{max}"
).to_i
end
def self.get_email
get_valid_input(
"Enter email address: ",
->(input) { input.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i) },
"Invalid email format"
)
end
def self.get_choice(options)
puts "Please select:"
options.each_with_index do |option, index|
puts " #{index + 1}. #{option}"
end
choice = get_valid_input(
"Enter selection (1-#{options.length}): ",
->(input) {
begin
num = Integer(input)
num >= 1 && num <= options.length
rescue ArgumentError
false
end
},
"Please enter a valid selection"
).to_i
options[choice - 1]
end
end
# Use input validator (interactive example)
=begin
# Get age
age = UserInputValidator.get_number_in_range(1, 120)
puts "Your age is: #{age}"
# Get email
email = UserInputValidator.get_email
puts "Your email is: #{email}"
# Get selection
choices = ["Option A", "Option B", "Option C"]
selected = UserInputValidator.get_choice(choices)
puts "You selected: #{selected}"
=end🎯 Loop Best Practices
1. Choose Appropriate Loop Method
ruby
# Good practice: Choose loop method based on scenario
# Known iteration count: Use times
5.times { |i| puts "Iteration #{i + 1}" }
# Numeric range: Use upto/downto
1.upto(10) { |i| puts i }
# Collection traversal: Use each
[1, 2, 3].each { |item| puts item }
# Data transformation: Use map
squared = [1, 2, 3].map { |n| n * n }
# Condition filtering: Use select
evens = [1, 2, 3, 4, 5].select(&:even?)2. Avoid Infinite Loops
ruby
# Good practice: Ensure loop has exit condition
counter = 0
while counter < 10
puts counter
counter += 1 # Ensure counter increases
end
# Use iterators instead of manual loops
# Not recommended:
i = 0
while i < array.length
process(array[i])
i += 1
end
# Recommended:
array.each { |item| process(item) }3. Use Loop Control Statements Reasonably
ruby
# Good practice: Clear control flow
def find_user(users, target_name)
users.each do |user|
return user if user[:name] == target_name
end
nil # Not found
end
# Avoid complex nested controls
# Not recommended:
users.each do |user|
if user[:active]
posts.each do |post|
if post[:author] == user[:name]
if post[:published]
# Complex processing logic
end
end
end
end
end
# Recommended: Break down complex logic
def process_user_posts(user, posts)
return unless user[:active]
user_posts = posts.select { |post|
post[:author] == user[:name] && post[:published]
}
user_posts.each { |post| process_post(post) }
end4. Performance Considerations
ruby
# Avoid repeated calculations in loops
# Not recommended:
array.each do |item|
expensive_calculation = calculate_something() # Calculated every time
process(item, expensive_calculation)
end
# Recommended:
expensive_calculation = calculate_something() # Calculated only once
array.each do |item|
process(item, expensive_calculation)
end
# Use appropriate methods instead of manual loops
# Not recommended:
sum = 0
numbers.each { |n| sum += n }
# Recommended:
sum = numbers.sum📚 Next Steps
After mastering Ruby loop statements, it is recommended to continue learning:
- Ruby Iterators - Learn more advanced iteration patterns
- Ruby Enumerators - Master enumerator usage
- Ruby Concurrent Programming - Learn multithreading and concurrent processing
- Ruby Performance Optimization - Learn loop performance optimization techniques
Continue your Ruby learning journey!