Loops and iterators
This chapter introduces the tools that allow you to repeat instructions: classic loops and iterators. We also discover how to read what the user types on the keyboard.
Principle
We already saw .each in chapter 3 for iterating over an Array. But sometimes, we need to repeat code without having a collection: waiting for a user action, simulating combat turns, or displaying a countdown.
Ruby provides two families of tools:
- Loops (
while,until,loop): repeat a block as long as a condition is true (or until we decide to stop). Useful when we don't know in advance how many times we'll loop. - Iterators (
times,upto,downto,each): iterate a fixed number of times or over a collection. This is the idiomatic Ruby way.
Reading user input
Before building menus, we need to know how to read what the user types. Ruby provides gets:
print 'What is your name? '
name = gets.chomp
puts "Hello, #{name}!"
printdisplays text without a newline (unlikeputs). The cursor stays on the same line, which is convenient for questions.getswaits for the user to type something and press Enter. It returns the text as a String..chompremoves the trailing newline\n. Without.chomp, the String would contain an invisible line break.
To read a number, add .to_i:
print 'Pokémon level: '
level = gets.chomp.to_i
puts "Level: #{level}"
.to_iconverts the String to an Integer. If the user types something other than a number,.to_ireturns 0.
The while loop
while repeats a block as long as the condition is true:
level = 1
while level < 5
puts "Pikachu is at level #{level}"
level += 1
end
Outputs:
Pikachu is at level 1
Pikachu is at level 2
Pikachu is at level 3
Pikachu is at level 4
- The condition
level < 5is evaluated before each iteration. Whenlevelreaches 5, the condition becomes false and the loop stops. level += 1is essential. Without it,levelwould stay at 1 and the loop would run forever (infinite loop). If that happens, press Ctrl+C to interrupt the program.
The until loop
until is the opposite of while: it repeats as long as the condition is false (in other words, until the condition becomes true):
hp = 50
max_hp = 120
until hp >= max_hp
hp += 10
puts "Healing... HP: #{hp}/#{max_hp}"
end
puts 'Healing complete!'
until hp >= max_hpreads as: "repeat until the HP reach the maximum".untilis sometimes more readable thanwhilewhen thinking in terms of "until".
The loop loop
loop creates an infinite loop. You exit it with break:
loop do
print 'Type "quit" to exit: '
input = gets.chomp
break if input == 'quit'
puts "You typed: #{input}"
end
puts 'Exited the loop!'
loop do ... endruns indefinitely until abreakis reached.break if input == 'quit'exits the loop when the user types "quit".- This is the ideal pattern for interactive menus: display the options, read the choice, process it, and start again.
The times iterator
times executes a block a fixed number of times:
3.times do
puts 'Pikachu uses Thunderbolt!'
end
You can also retrieve the iteration number:
5.times do |turn|
puts "Turn #{turn + 1}"
end
Outputs:
Turn 1
Turn 2
Turn 3
Turn 4
Turn 5
|turn|receives the iteration number, starting at 0. We add 1 for human-readable display.timesis perfect when you know the number of repetitions in advance.
The upto and downto iterators
To iterate over a range of numbers:
# Count from 1 to 5
1.upto(5) do |level|
puts "Level #{level}"
end
# Countdown
5.downto(1) do |count|
puts "#{count}..."
end
puts 'Evolution!'
1.upto(5)iterates from 1 to 5 inclusive, in ascending order.5.downto(1)iterates from 5 to 1 inclusive, in descending order.
Reminder: each and each_with_index
We already saw each in chapter 3. It is the iteration method in Ruby:
team = ['Pikachu', 'Charizard', 'Blastoise']
team.each { |pokemon| puts "Go, #{pokemon} !" }
team.each_with_index do |pokemon, index|
puts "#{index + 1}. #{pokemon}"
end
To iterate over a Hash (seen in chapter 4), the block receives the key and the value:
stats = { hp: 35, attack: 55, defense: 40 }
stats.each do |stat, value|
puts "#{stat} : #{value}"
end
The for loop
for exists in Ruby but is almost never used. We mention it because you may encounter it in older code:
types = [:fire, :water, :grass]
for type in types
puts "Type : #{type}"
end
foris syntactic sugar foreach, but with a drawback: the variabletypecontinues to exist after the loop. Witheach, the block variable is local. That is why Ruby developers prefereach.
Flow control: break and next
break -- exit the loop
break exits the loop immediately:
team = ['Pikachu', 'Charizard', 'Blastoise', 'Venusaur']
team.each do |pokemon|
break if pokemon == 'Blastoise'
puts "Checking #{pokemon}..."
end
puts 'Blastoise found!'
Outputs:
Checking Pikachu...
Checking Charizard...
Blastoise found!
- As soon as
pokemonequals'Blastoise',breakstops theeach. The remaining elements are not iterated.
next -- skip to the next iteration
next skips the rest of the block and moves to the next element:
levels = [5, 0, 12, 0, 8]
levels.each do |level|
next if level == 0
puts "Level #{level} Pokémon"
end
Outputs:
Level 5 Pokémon
Level 12 Pokémon
Level 8 Pokémon
next if level == 0skips Pokemon with level 0 (fainted). Theputsis not executed for them, but the loop continues with the remaining elements.
Conclusion
whilerepeats as long as a condition is true.untilrepeats until it becomes true.loopwithbreakis the standard pattern for interactive menus.timesrepeats a fixed number of times.upto/downtoiterate over a range.eachis the idiomatic method for iterating over collections. Prefereachoverfor.breakexits the loop.nextskips to the next iteration.gets.chompreads user input..to_iconverts it to a number.printdisplays text without a newline (useful for questions).