Les boucles

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 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 entre l et n, l et n inclus ;
  • l...n désigne aussi l’ensemble des nombres entre l et n, mais n 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 boucle until 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 infinie loop.
  • La boucle for nous permet de parcourir un ensemble, mais nous préférons utiliser des itérateurs comme each.