Les boucles et les itérateurs
Ce chapitre présente les outils qui permettent de répéter des instructions : les boucles classiques et les itérateurs. On découvre aussi comment lire ce que l'utilisateur tape au clavier.
Principe
On a déjà vu .each au chapitre 3 pour parcourir un Array. Mais parfois, on a besoin de répéter du code sans avoir de collection : attendre une action de l'utilisateur, simuler des tours de combat, ou afficher un compte à rebours.
Ruby propose deux familles d'outils :
- Les boucles (
while,until,loop) : répètent un bloc tant qu'une condition est vraie (ou jusqu'à ce qu'on décide d'arrêter). Utiles quand on ne sait pas à l'avance combien de fois on va boucler. - Les itérateurs (
times,upto,downto,each) : parcourent un nombre fixe de fois ou une collection. C'est la manière idiomatique de Ruby.
Lire une entrée utilisateur
Avant de construire des menus, il faut savoir lire ce que l'utilisateur tape. Ruby offre gets :
print 'Quel est ton nom ? '
name = gets.chomp
puts "Bonjour, #{name} !"
printaffiche du texte sans retour à la ligne (contrairement àputs). Le curseur reste sur la même ligne, ce qui est pratique pour les questions.getsattend que l'utilisateur tape quelque chose et appuie sur Entrée. Il retourne le texte sous forme de String..chompsupprime le retour à la ligne\nà la fin. Sans.chomp, la String contiendrait un saut de ligne invisible.
Pour lire un nombre, on ajoute .to_i :
print 'Niveau du Pokémon : '
level = gets.chomp.to_i
puts "Niveau : #{level}"
.to_iconvertit la String en Integer. Si l'utilisateur tape autre chose qu'un nombre,.to_iretourne 0.
La boucle while
while répète un bloc tant que la condition est vraie :
Affiche :
Pikachu est au niveau 1
Pikachu est au niveau 2
Pikachu est au niveau 3
Pikachu est au niveau 4
- La condition
level < 5est évaluée avant chaque tour. Quandlevelatteint 5, la condition devient fausse et la boucle s'arrête. level += 1est indispensable. Sans lui,levelresterait à 1 et la boucle tournerait indéfiniment (boucle infinie). Si ça arrive, appuyer sur Ctrl+C pour interrompre le programme.
La boucle until
until est l'inverse de while : elle répète tant que la condition est fausse (autrement dit, jusqu'à ce que la condition devienne vraie) :
hp = 50
max_hp = 120
until hp >= max_hp
hp += 10
puts "Soin en cours... PV : #{hp}/#{max_hp}"
end
puts 'Soin terminé !'
until hp >= max_hpse lit : "répéter jusqu'à ce que les PV atteignent le maximum".untilest parfois plus lisible quewhilequand on pense en termes de "jusqu'à ce que".
La boucle loop
loop crée une boucle infinie. On en sort avec break :
loop do
print 'Tape "quit" pour sortir : '
input = gets.chomp
break if input == 'quit'
puts "Tu as tapé : #{input}"
end
puts 'Sorti de la boucle !'
loop do ... endtourne indéfiniment jusqu'à ce qu'unbreaksoit atteint.break if input == 'quit'quitte la boucle quand l'utilisateur tape "quit".- C'est le pattern idéal pour les menus interactifs : on affiche les options, on lit le choix, on traite, et on recommence.
L'itérateur times
times exécute un bloc un nombre fixe de fois :
3.times do
puts 'Pikachu utilise Tonnerre !'
end
On peut aussi récupérer le numéro du tour :
5.times do |turn|
puts "Tour #{turn + 1}"
end
Affiche :
Tour 1
Tour 2
Tour 3
Tour 4
Tour 5
|turn|reçoit le numéro de l'itération, en commençant à 0. On ajoute 1 pour un affichage humain.timesest parfait quand on connaît le nombre de répétitions à l'avance.
Les itérateurs upto et downto
Pour parcourir un intervalle de nombres :
# Compter de 1 à 5
1.upto(5) do |level|
puts "Niveau #{level}"
end
# Compte à rebours
5.downto(1) do |count|
puts "#{count}..."
end
puts 'Évolution !'
1.upto(5)parcourt de 1 à 5 inclus, dans l'ordre croissant.5.downto(1)parcourt de 5 à 1 inclus, dans l'ordre décroissant.
Rappel : each et each_with_index
On a déjà vu each au chapitre 3. C'est la méthode de parcours en Ruby :
Pour parcourir un Hash (vu au chapitre 4), le bloc reçoit la clé et la valeur :
stats = { hp: 35, attack: 55, defense: 40 }
stats.each do |stat, value|
puts "#{stat} : #{value}"
end
La boucle for
for existe en Ruby mais n'est presque jamais utilisée. On la mentionne parce qu'on peut la croiser dans du code ancien :
types = [:fire, :water, :grass]
for type in types
puts "Type : #{type}"
end
forest du sucre syntaxique poureach, mais avec un défaut : la variabletypecontinue d'exister après la boucle. Aveceach, la variable du bloc est locale. C'est pour ça que les développeurs Ruby préfèrenteach.
Contrôle de flux : break et next
break — sortir de la boucle
break quitte la boucle immédiatement :
team = ['Pikachu', 'Dracaufeu', 'Tortank', 'Florizarre']
team.each do |pokemon|
break if pokemon == 'Tortank'
puts "Vérification de #{pokemon}..."
end
puts 'Tortank trouvé !'
Affiche :
Vérification de Pikachu...
Vérification de Dracaufeu...
Tortank trouvé !
- Dès que
pokemonvaut'Tortank',breakarrête leeach. Les éléments suivants ne sont pas parcourus.
next — passer à l'itération suivante
next saute le reste du bloc et passe à l'élément suivant :
levels = [5, 0, 12, 0, 8]
levels.each do |level|
next if level == 0
puts "Pokémon de niveau #{level}"
end
Affiche :
Pokémon de niveau 5
Pokémon de niveau 12
Pokémon de niveau 8
next if level == 0saute les Pokémon de niveau 0 (KO). Leputsn'est pas exécuté pour eux, mais la boucle continue avec les éléments suivants.
Conclusion
whilerépète tant qu'une condition est vraie.untilrépète jusqu'à ce qu'elle le devienne.loopavecbreakest le pattern standard pour les menus interactifs.timesrépète un nombre fixe de fois.upto/downtoparcourent un intervalle.eachest la méthode idiomatique pour parcourir les collections. Préférereachàfor.breaksort de la boucle.nextsaute à l'itération suivante.gets.chomplit l'entrée utilisateur..to_ila convertit en nombre.printaffiche sans retour à la ligne (utile pour les questions).