Il est maintenant temps d’apprendre à répéter des instructions un certain nombre de fois. Les boucles ont pour objectif de permettre de faire ceci. Il existe plusieurs boucles différentes, et ce sont elles que nous allons voir durant ce chapitre.
- La boucle while
- La boucle for
- Contrôler l’exécution d’une boucle
- La boucle loop
- Les itérateurs
- Exercices
La boucle while
La boucle while
est une structure de boucle plutôt simple. Elle permet de répéter des instructions tant qu’une condition est vraie. Encore une fois, remarquons que while
est l’équivalent anglais de « tant que ».
Prenons le cas d’un programme qui affiche la table de multiplication d’un nombre. Il affiche les produits de ce nombre par les entiers de 1 à 10.
n = 1
while n <= 10 # Tant que n est inférieur ou égal à 10, le code est exécuté.
print "#{n * 8} "
n = n + 1 # On ajoute 1 à n à chaque tour pour que sa valeur atteigne 10.
end
Ici, nous affichons la table de 8. Pour cela, nous initialisons une variable à 1. Cette variable s’appelle un compteur. Tant que cette variable sera inférieure ou égal à 10, on affichera le résultat du produit de 8 et de cette variable, puis on ajoutera 1 à cette variable (on dit qu’on incrémente la variable).
Là encore, le mot-clé end
est obligatoire.
Exécutons le programme et observons le résultat.
8 16 24 32 40 48 56 64 72 80
La structure d’une boucle while
est vraiment très proche de celle d’un if
. On pourrait presque se dire que c’est un if
qui se répète tant que la condition est vraie.
D’ailleurs, nous pouvons aussi utiliser une forme condensée de while
. On pourrait alors écrire le programme précédent de cette manière.
n = 1
(print "#{n * 8} "; n = n + 1) while n <= 10
Tout comme pour le if
et sa forme condensée, nous allons préférer la forme condensée du while
à sa forme classique s’il n’y a qu’une seule instruction à exécuter et que celle-ci est courte.
La structure begin
-while
Il peut arriver que nous voulions qu’une instruction se répète en fonction d’une condition (donc une boucle while
) , mais que nous voulions également que cette instruction soit exécutée au moins une fois. Nous pouvons nous dire qu’il suffit de placer l’instruction une fois avant la boucle, et une autre fois dans la boucle, et c’est bien vrai. Mais il y a mieux.
La structure begin
-end while
équivaut à un while
, sauf que le code est exécuté au moins une fois. Avec cette structure, le while
se retrouve à la toute fin de la boucle. Reprenons l’exemple précédent en utilisant cette structure.
n = 1
begin
print "#{n * 8} "
n = n + 1
end while n <= 10
Cet exemple n’est peut-être pas très parlant. Regardons alors celui-là.
n = 6
begin
print "#{n} "
end while n < 5
La condition n’est même pas vraie au premier tour de boucle, mais les instructions sont toujours exécutées au moins une fois. En fait, la condition n’est évaluée qu’après le premier tour de boucle, et ceci car elle est évaluée après l’exécution des instructions (le while
est à la fin pour cette raison).
Encore une fois, le end
est obligatoire, et là, il est à placer avant le while qui se retrouve alors à la fin de la boucle.
En dehors de ceci, la structure begin
-while
fonctionne exactement comme la structure while
. Il faut cependant faire attention à la forme condensée et ne pas oublier d’y intégrer le end
de cette manière.
n = 11
begin (print "#{n * 8} "; n = n + 1) end while n <= 10
La structure until
Rendons à César ce qui est à César : la boucle until
est à la boucle while
exactement ce que la condition unless
est à la condition if
. Ainsi, la boucle until
est une boucle qui s’exécute jusqu’à ce que la condition soit vraie, c’est-à-dire tant qu’elle est fausse. On a donc until (condtition)
qui est équivalent à while !(condition)
. Réécrivons le programme précédent avec une boucle until
.
n = 1
until n > 10 # Jusqu’à ce que n soit plus grand que 10, le code est exécuté.
print "#{n * 8} "
n = n + 1
end
Remarquons que l’inégalité large que l’on avait avec la boucle while
devient stricte avec la boucle until
. En effet, on veut s’arrêter quand n
sera strictement supérieur à 10.
Encore une fois, et nous pouvions nous en douter, nous pouvons utiliser la structure condensée.
n = 1
(print "#{n * 8} "; n = n + 1) until n > 10
Nous allons préférer until
à while
dans le cas d’une condition négative.
# Mauvais code.
while !condition
# Instructions.
end
# Bon code.
until condition
# Instructions.
end
La boucle for
Pour le moment, nous avons vu la boucle while
et ses dérivés. Elle permet d’exécuter des instructions en fonction d’un condition. La boucle for
est relativement similaire : elle permet d’exécuter des instructions pour chaque élément d’un ensemble.
Mais de quoi voulons-nous parler en parlant d’« ensemble » ? En fait, nous en avons déjà utilisé dans la partie précédente lorsque nous avons parlé des intervalles de nombres. Oui, c’est un ensemble. La boucle for
nous permet alors d’effectuer des instructions pour chaque élément d’un intervalle.
Le code suivant exécute l’instruction print n
de manière répétée.
for n in 0..5
print "#{n} " # Instruction(s).
end
Nous devons encore le redire, le end
est obligatoire.
Sans surprise, ce code affiche tous les entiers entre 0 et 5.
0 1 2 3 4 5
Il existe une autre façon d’écrire des intervalles. Elle ressemble beaucoup à celle que nous avons déjà vue. En fait, pour l’utiliser, il suffit d’écrire ...
plutôt que ..
. Cependant, il y a une différence entre les deux syntaxes :
l..n
désigne l’ensemble des nombres entrel
etn
,l
etn
inclus ;l...n
désigne aussi l’ensemble des nombres entrel
etn
, maisn
est cette fois exclu.
Ainsi, le code…
for n in 0...5
print "#{n} " # Instruction(s).
end
… n’affichera que…
0 1 2 3 4
Notons que si dans la partie précédente, le when
évaluait si le nombre proposé se trouvait dans l’intervalle, nombres réels compris, ici, l’intervalle est parcouru d’entier en entier.
En y réfléchissant, ceci est tout à fait normal : si on commence à 2.3 par exemple, comment savoir si on doit avancer de 1 en 1, ou si on doit commencer à 2.3 puis passer à 3…
Les deux façons de noter les intervalles peuvent troubler et il est facile d’oublier celui qui inclut la borne supérieure. Voici un moyen mnémotechnique simple pour ne pas se tromper. Quand on écrit a...b
, le troisième point est là pour « pousser b
dehors » ; avec a...b
, b
est donc exclu.
Contrôler l’exécution d’une boucle
Ce serait bien de pouvoir rajouter un peu de contrôle sur nos boucles. Tant mieux, Ruby dispose de mots-clés réservés au contrôle de l’exécution des boucles.
Le mot-clé break
Le mot-clé break
permet d’interrompre l’exécution d’une boucle à n’importe quel moment. Son fonctionnement est vraiment très simple.
i = 0
while true
print "#{i} "
break if i > 6
i = i + 1
end
Voici le résultat obtenu.
1 2 3 4 5 6 7
Quand i
vaut 7, on rentre dans le if
et le break
est exécuté : on sort de la boucle au septième tour de boucle.
Le mot-clé next
Le mot-clé next
permet d’interrompre un tour de boucle. Le programme passe alors directement au tour suivant. Voici, par exemple, une manière (pas la plus futée) d’afficher les nombres impairs entre 0 et 10.
for i in 0..10
next if i % 2 == 0
print "#{i} "
end
On obtient sans surprise ce résultat.
1 3 5 7 9
Pour ce faire, on passe au tour de boucle suivant si i
est pair, c’est-à-dire si i % 2 = 0
. L’instruction print i
n’est donc exécutée que si i
est impair.
Notons que l’utilisation de next
dans ce genre de situation est préférée à l’utilisation d’un bloc conditionnel dans une boucle. Donc, le code que nous avons écrit sera préféré à celui-là.
for i in 0..10
print "#{i} " if i % 2 != 0
end
Le mot-clé redo
Le mot-clé redo
, lui, permet de recommencer un tour de boucle. Si on est au troisième tour de boucle et que le programme croise redo
, le troisième tour de boucle recommence. Affichons deux fois tous les entiers de 1 à 5.
k = 1
for i in 1..5
puts i
if i == k
k = k + 1
redo
end
end
Ici, on a un compteur i
pour la boucle et un autre k
qui permet de savoir quand on doit reprendre un tour de boucle. Et on incrémente k
à chaque fois que i == k
.
La boucle loop
Il est maintenant temps de parler d’une boucle particulière : la boucle loop
. Elle a de particulier qu’elle n’a pas de condition d’arrêt, c’est une boucle infinie. Elle est donc équivalente à while true
. Mais nous pouvons quand même en sortir. Comment ? Tout simplement en utilisant un des mots-clés que nous venons de voir. Oui, le mot-clé break
permet de sortir d’une boucle infinie. Testons d’abord ce code (attention, ce programme boucle indéfiniment, il nous faudra appuyer sur Ctrl + C pour quitter le programme).
i = 0
loop do
puts i
i = i + 1
end
Et regardons celui-là qui s’arrête à 10 grâce à break
.
i = 0
loop do
puts i
break if i == 10
i = i + 1
end
Le do
est obligatoire, de même que le end
. En fait, do
peut aussi être utilisé de la même manière avec les boucles while
, until
et for
. Le code de la boucle est alors encadré par do
et end
de même qu’il est encadré par begin
et end
dans la structure begin
-while
.
Tout comme nous avons dit que les structures conditionnelles que nous utiliserons le plus sont if
-else
et case
-when
, nous devons avouer que les boucles les plus courantes sont les boucles while
et for
. La boucle loop
que nous venons de voir est préférée à la structure begin
-while
.
Les itérateurs
En fait, la boucle for
est rarement utilisée pour faire des itérations. À la place, nous utilisons ce que nous appelons des itérateurs. Chaque type d’ensemble a ses propres itérateurs (même s’ils en ont parfois en commun) qui permettent de faire plusieurs opérations.
La méthode each
Pour parcourir un intervalle, nous utiliserons la méthode each
. Par exemple, avec ce code, nous affichons tous les nombres de l’intervalle 1..8
.
(1..8).each do |i|
puts i
end
Ici, pour chaque (each) élément de l’intervalle 1..8
, nous affichons i
, avec i
qui correspond à chaque fois à un élément de l’intervalle parcouru.
La structure do
-end
délimite ce qu’on appelle un bloc. On donne un bloc à la méthode each
et elle l’exécute pour chaque élément de l’ensemble que l’on parcourt. Un bloc peut également être délimité par des accolades. Il est d’usage d’utiliser les accolades quand notre bloc s’écrit sur une ligne et la structure do
-end
lorsqu’il est plus long. Ainsi, notre code précédent peut également s’écrire ainsi.
(1..8).each { |i| puts i }
Notons que contrairement au code interpolé, le code mis entre accolades pour un bloc est ici entouré d’espaces. C’est une bonne pratique qu’il nous faut essayer de respecter.
Il y a quelques différences subtiles entre la délimitation par do
-end
ou par les accolades, mais nous n’en parlerons pas ici. Nous devons juste savoir que l’on donne à notre itérateur un bloc à exécuter pour chaque élément de notre intervalle.
Dans notre code, i
est une variable du bloc. Lorsque nous donnons un bloc avec une variable à each
, cette variable prend tour à tour toutes les valeurs de l’ensemble parcouru.
Les blocs sont une structure puissante de Ruby dont nous ne parlerons pas plus pour le moment.
La méthode times
Pour faire une opération un certain nombre de fois, nous utiliserons la méthode times
. Elle s’applique à un entier k
et exécute le bloc qu’on lui fournit k
fois.
5.times { |i| print "#{i} " }
Ici, la variable i
variera de 0 à 4, et à chaque fois, nous allons afficher i
. Voici l’équivalent de ce code en utilisant une boucle for
.
for n in 0...5
print "#{n} "
end
times
s’exécute n
fois, et la variable i
de notre code va de 0
à 4
, pas de 0
à 5
.
La méthode upto
Si nous ne voulons par partir de 0 mais d’un autre nombre, nous pouvons utiliser la méthode upto
. Elle s’applique à un entier, l’incrémente et exécute un bloc d’instructions jusqu’à atteindre le nombre passé en paramètre.
2.upto(5) { |i| print "#{i} " }
Ce code est équivalent à celui-là.
for n in 2..5
print "#{n} "
end
La méthode downto
Et si nous voulons aller de 5 à 2, nous utilisons la méthode downto
qui s’applique à un entier, le décrémente et exécute un bloc de code jusqu’à atteindre le nombre passé en paramètre.
5.downto(2) { |i| print "#{i} " }
Qui avec une boucle for
pourrait s’écrire ainsi, avec un code plus compliqué.
for n in 2..5
print "#{5 - n + 2} "
end
La méthode step
Il est également possible d’aller de 2 en 2 ou de -4 en -4 grâce à la méthode step
. Elle s’applique à un entier début
, prend en paramètre deux entiers fin
et pas
, et permet d’aller de début
à fin
en allant de pas
en pas
. Par exemple, avec le code suivant, nous comptons de 5 en 5 en commençant à 2 pour aller jusqu’à 18.
2.step(18, 5) { |i| puts i }
À chaque fois que nous avons utilisé un itérateur, nous avions une variable entourée du symbole | |
qui prenait successivement toutes les valeurs parcourues. C’est un paramètre du bloc. Mais elle n’est pas obligatoire. Par exemple, si nous voulons juste afficher 4 fois le message « Voici un message », nous pouvons parfaitement écrire ceci.
4.times { puts 'Voici un message.' }
Cependant, nous allons préférer toujours mettre le paramètre de bloc, et s’il est inutilisé, nous allons le préfixer par _
(il pourra même être _
). Nous allons donc plutôt écrire ce code.
4.times { |_| puts 'Voici un message.' }
En fait, dès qu’une variable sera inutilisée (oui, ça arrive parfois), nous allons la préfixer par le symbole _
.
Nous verrons plus tard d’autres itérateurs lorsque nous verrons d’autre types d’ensembles. Mais, il nous faut savoir que les itérateurs sont à privilégier sur les boucles.
Exercices
Comme d’habitude, terminons ce chapitre par une petite série d’exercices.
Exercice 1
Écrivons un programme qui demande un entier n
à l’utilisateur et qui affiche la somme des nombres de 1 à n
, si n
est positif, et retourne une erreur sinon.
Correction.
Pour ce faire, nous allons initialiser une variable somme
à 0, puis nous allons ajouter les entiers à cette variable pour k
allant de 1 à n.
print 'Entrez un nombre positif : '
n = gets.chomp.to_i
if n < 0
print 'Erreur, votre nombre n’est pas positif.'
else
somme = 0
1.upto(n) { |k| somme += k }
print somme
end
Exercice 2
Écrire un programme qui demande un entier n
à l’utilisateur et affiche un triangle de n
lignes composées d’étoiles. Pour n = 4
, le programme doit afficher ceci.
*
**
***
****
Correction.
Le principe est un peu le même que pour l’exercice 1. Cependant, il faut cette fois utiliser deux boucles imbriquées (ou deux itérateurs). La première pour le nombre de lignes à afficher, la seconde pour le nombre d’étoiles à afficher par ligne, en sachant que lorsqu’on est à la ligne n
, on affiche n
étoiles.
print 'Entrez un entier positif : '
n = gets.chomp.to_i
if n < 0
print 'Votre nombre n’est pas positif.'
else
1.upto(n) do |k|
k.times { print "*" }
puts
end
end
Exercice 3
Nous en avons l’habitude, il faut aussi des exercices compliqués. Ici, nous allons écrire un programme qui demande un nombre entier à l’utilisateur et qui affiche ensuite ce nombre à l’envers. Un exemple d’utilisation du programme.
Entrez un nombre entier : 1974
4791
Aide.
Un indice : utiliser le modulo. En effet, on a :
4791 % 10 = 1
;479 % 10 = 9
;47 % 10 = 7
;4 % 10 = 4
.
Nous pouvons ainsi obtenir tous les chiffres à afficher.
Correction.
print 'Entrez un nombre : '
number = gets.chomp.to_i
until number == 0
print number % 10
number = number / 10
end
En cas de non-compréhension de ce programme, il faut prendre un nombre, et dérouler l’algorithme sur lui à l’aide d’un crayon et d’un papier.
Maintenant, nos programmes peuvent être encore plus vivants.
- Les boucles permettent de répéter des instructions. La boucle
while
permet de répéter tant qu’une condition est satisfaite et la boucleuntil
jusqu’à ce qu’elle soit satisfaite. - Certains mots-clés nous permettent de contrôler l’exécution d’une boucle pour, par exemple, en sortir (mot-clé
break
) ce qui nous permet de sortir de la boucle infinieloop
. - La boucle
for
nous permet de parcourir un ensemble, mais nous préférons utiliser des itérateurs commeeach
.