Les conditions

Dans ce chapitre nous apprendrons à créer des conditions. Ceci permettra d’adapter le déroulement de nos programmes à certains paramètres, tels que la valeur des variables.

Opérateurs de comparaison

Les conditions (ainsi que les boucles) introduisent de nouveaux opérateurs.
Voici le tableau des opérateurs de comparaison.

Opérateur Signification
< Strictement inférieur à
> Strictement supérieur à
<= Inférieur ou égal à
>= Supérieur ou égal à
== Égal à
!= Différent de

Ainsi, on a 2 < 3 qui est vrai et 3 != 2 qui est aussi vrai.

Essayons de taper quelques expressions avec ces signes sur IRB.

2 > 1
=> true
3 > 6.0
=> false
'Trois' == 'Trois'
=> true

Ces expressions renvoient true ou false. Ce sont ce que l’on appelle des booléens. Une variable booléenne ne peut prendre comme valeur que true ou false, c’est-à-dire vrai ou faux. Parfait pour faire des conditions.

Notons que la comparaison entre chaînes de caractères se fait suivant l’ordre lexicographique (l’ordre du dictionnaire). Ainsi, 'Trois' > 'Quatre' renverra true. L’ordre de Ruby est le suivant : d’abord les chiffres (0, 1, 2… 9), puis les lettres majuscules (A, B… Z), puis les lettres minuscules (a, b… z).

En fait, Ruby compare les caractères des chaînes en utilisant la valeur de leur code UTF-8 (par exemple, 'é' > '#' vaut true).

Nous pouvons bien sûr créer des variables de type booléen. Par exemple…

boolean = true
false_boolean = false

Ces booléens nous servent justement à construire nos conditions. Toutes les comparaisons que nous ferons vaudront soit true, soit false et il nous faudra juste évaluer cette valeur.

On peut aussi combiner ces opérateurs et faire des comparaisons plus avancées avec trois mots-clés :

  • and qui signifie « et » permet d’écrire 3 > 2 and 3 < 5 (ce qui est vrai) ;
  • or qui signifie « ou » permet d’écrire 3 > 2 or 2 < 5 (ce qui est vrai) ;
  • not qui signifie « non », c’est-à-dire la condition contraire, permet d’écrire not 3 < 2 (ce qui est vrai).

L’on peut remplacer and par &&, or par || et not par !. En fait, nous allons même privilégier ! && et || pour les expressions booléennes, conformément aux bonnes pratiques de Ruby.

Mise entre parenthèses

Nous pouvons placer des parenthèses autour de vos comparaisons. Si nous les plaçons autour de toute la condition, cela ne change pas sa valeur. (1 == 2 || 2 == 2) est strictement équivalent à 1 == 2 || 2 == 2. Cependant, si nous plaçons des parenthèses autour d’une partie de la condition, sa valeur peut changer. En effet, cela change les priorités. Ainsi :

  • (1 == 1 || 2 == 3) && 3 == 4 vaut false ;
  • 1 == 1 || (2 == 3 && 3 == 4) vaut true.

Voyons ce qui se passe dans le premier cas.

1 == 1 || 2 == 3 vaut true, donc c’est équivalent à true && 3 == 4, avec 3 ≠ 4, donc (1 == 1 || 2 == 3) && 3 == 4 vaut false.

Dans le deuxième cas maintenant.

2 == 3 && 3 == 4 vaut false, donc c’est équivalent à 1 == 1 or false, avec 1 = 1, donc 1 == 1 || (2 == 3 && 3 == 4) vaut true.

La structure if-else

La structure if-else est la plus simple des structures conditionnelles. On évalue une expression (on regarde si elle vaut true ou false) ; si (if) elle vaut true, on exécute certaines instructions, sinon (else), on en exécute d’autres.

Prenons le cas d’un programme qui demande l’âge de l’utilisateur et nous dit s’il est majeur ou mineur. S’il a moins de 18 ans, il est mineur, sinon, il est majeur.

age = gets.chomp.to_i # On récupère une saisie et on la convertit en entier.

if age < 18
  print 'Vous êtes mineur.'
else     # Sinon…
  print 'Vous êtes majeur.'
end

Ici, nous vérifions si la variable age est inférieure à 18 grâce à <. Rentrons différents âges inférieurs ou supérieurs à 18 et admirons le travail.

Cette structure conditionnelle se termine toujours par le mot-clé end. C’est très important, et il ne faut pas l’oublier.

L’indentation n’est pas obligatoire comme en Python. Cependant, il faut quelque chose pour séparer le if condition des instructions à exécuter. Le retour à la ligne est l’un des moyens de faire cette séparation. Le mot clé then (« alors ») est l’autre manière de la faire. On peut alors écrire ce code strictement équivalent au précédent.

age = gets.chomp.to_i # On récupère une saisie et on la convertit en entier.

if age < 18 then print 'Vous êtes mineur.' else print 'Vous êtes majeur.' end

Mais nous allons plutôt privilégier la première écriture. Notons de plus qu’il est conseillé d’indenter en utilisant deux espaces. Nous pouvons combiner le then et le retour à ligne, mais ce n’est pas une pratique conseillée.

Notons que le else n’est pas obligatoire. Nous pouvons parfaitement écrire ceci.

age = gets.chomp.to_i # On récupère une saisie et on la convertit en entier.

if age < 18
  print 'Vous êtes mineur.'
end
Le mot-clé elsif

Si nous avons plus de deux possibilités, nous pouvons compléter notre structure if-else avec un elsif (sinon si). Il permet d’ajouter une autre possibilité à la condition if. Prenons le code précédent et rajoutons un elsif.

age = gets.chomp.to_i

if age < 18    # Si âge est inférieur à 18…
  print 'Vous êtes mineur.'
elsif age > 80 # Si âge est supérieur à 80…
  print 'Vous êtes senior.'
else           # Sinon…
  print 'Vous êtes majeur et votre âge est inférieur à 80 ans.'
end

On regarde si la première condition est vraie. Si elle est vraie, on affiche que l’utilisateur est mineur, sinon, on regarde la seconde condition, et enfin, on arrive au else qui s’exécute si aucune des conditions précédentes n’est vraie.

Nous pouvons utiliser autant de elsif que nous voulons.

Ce n’est vraiment pas compliqué à utiliser. La seule subtilité est que les remarques que nous avons tenues à propos du if sont aussi valables pour le elsif : là encore, le retour à la ligne est obligatoire à moins que nous n’utilisions le mot-clé then.

Notons qu’il existe une façon plus concise d’écrire un if.

age = gets.chomp.to_i

print 'Vous êtes majeur.' if age >= 18 

Dans ce cas, il n’y a pas de end et l’instruction à exécuter doit être unique. Cependant, nous pouvons placer plusieurs instructions en utilisant le point-virgule et des parenthèses de cette manière.

age = gets.chomp.to_i

(print 'Vous êtes majeur.'; print 'Peut-être même que vous êtes senior.') if age >= 18 

Cette notation est conseillée pour les conditions sur une seule ligne, en particulier s’il n’y a qu’une seule instruction à exécuter et qu’elle est simple. Cependant, si la condition —- ou même l’instruction —- est trop compliquée, nous allons privilégier un if classique.

La structure unless

La structure unless fonctionne exactement de la même manière que if, et est en fait la condition contraire de if. Avec unless, les instructions sont toujours exécutées sauf lorsque la condition est vérifiée. On a donc unless (condition) qui est équivalent à if !(condition). Pour l’âge, on peut donc écrire ce programme.

age = 19

unless age < 18
  print 'Vous êtes majeur.'
else
  print 'Vous êtes mineur.'
end

Là encore, nous pouvons utiliser la structure condensée.

print 'Vous êtes majeur.' unless age < 18 

Une bonne pratique consiste à privilégier unless pour les conditions négatives, c’est-à-dire que nous allons préférer écrire ce genre de code.

# Mauvais code.
if !condition
  # Instructions.
end 

# Bon code.
unless condition
  # Instructions.
end 

Cependant, nous n’allons pas utiliser unless avec else, mais préférer réécrire la condition en changeant le unless par un if et en inversant les conditions.

# Mauvais code.
unless condition
  # Autres instructions.
else
  # Instructions.
end

# Bon code.
if condition_inverse
  # Instructions.
else
  # Autres instructions.
end

Hormis cela, les bonnes pratiques pour unless sont les mêmes que celles pour if.

La structure case-when

La structure case-when est une autre structure de condition. Elle permet de tester une suite de conditions sur une variable. Si la variable vaut telle valeur, alors fais ceci, sinon, si elle vaut telle autre valeur, alors fais cela… Par exemple, écrivons un programme qui demande à l’utilisateur d’entrer « Un », « Deux » ou « Trois » et qui affiche la valeur numérique du nombre entré.

number = gets.chomp

case number     # On teste la valeur de nombre.
when 'Un'    # Si c’est "Un"…
  print 1
when 'Deux'  # Sinon, si c’est "Deux"…
  print 2
when 'Trois' # Sinon, si c’est "Trois"…
  print 3
end   

Là encore, il y a un end. Il est aussi obligatoire.

Notons que nous pouvons rajouter un else pour faire quelque chose si aucun des cas testés n’est vérifié.

number = gets.chomp

case number
when 'Un'
  print 1
when 'Deux'
  print 2
when 'Trois'
  print 3
else
  print 'La saisie n’est pas bonne.'
end

Si la saisie de l’utilisateur n’est ni « Un », ni « Deux », ni « Trois », alors on affiche « La saisie n’est pas bonne. ».

Notons que nous pouvons écrire le when et l’action à effectuer sur une seule ligne en utilisant le mot-clé then. Ainsi, le code qui suit est strictement équivalent à celui que nous avons écrit plus haut.

number = gets.chomp

case number 
when 'Un' then print 1
when 'Deux' then print 2
when 'Trois' then print 3
else print 'La saisie n’est pas bonne.' 
end
Les intervalles

Cependant, là où case s’avère vraiment utile, c’est qu’il permet de tester si une valeur est dans un intervalle.

En Ruby, nous allons noter un intervalle x..y, qui représente l’intervalle [x,y][x, y] (donc xx et yy sont inclus). Pour voir comment nous pouvons utiliser cela, prenons l’exemple d’un programme qui vérifierait l’admission ou la mention selon une note à un examen. Dans notre cas, avoir en dessous de 6 est un échec, au-dessus de 12 la mention « Assez bien », de 14 la mention « Bien » et de 16 la mention « Très bien ».

Faire ce programme avec des if et des else serait long et contraignant. La structure case permet de simplifier les choses en offrant plusieurs choix.

mark = 18 # Nous pouvons modifier la note et voir ce qu’on obtient.

case mark
when 0..6
  print 'Vous n’avez pas réussi l’examen.'
when 6..12
  print 'Vous avez réussi l’examen.'
when 12..14
  print 'Vous avez réussi l’examen avec mention « Assez bien ».'
when 14..16
  print 'Vous avez réussi l’examen avec mention « Bien ».'
when 16..20
  print 'Vous avez réussi l’examen avec mention « Très bien ».'
else
  print 'La note entrée est incorrecte.'
end

Ça aurait été vraiment embêtant de faire tout ça avec des if et des else.

Les intervalles peuvent aussi se faire avec des valeurs flottantes. Ainsi, nous pouvons choisir d’attribuer la mention when 16.5..20.

Les conditions ternaires

Dans plusieurs langages, il y a ce que l’on appelle des conditions ternaires. Elles sont un moyen condensé d’écrire des conditions. Voici le schéma qu’elles suivent.

condition ? (si true) : (sinon)  

Ce n’est pas aussi impressionnant que ça en a l’air. Il faut bien sûr s’habituer à la syntaxe, mais une fois celle-ci assimilée, il faut s’exercer pour réussir à la maîtriser.

age = 19

(age < 18) ? (print 'Mineur') : (print 'Majeur')

Et on peut même utiliser des conditions ternaires imbriquées pour obtenir du else if.

age = 19

(age < 18) ? (print 'Mineur') : (age < 80 ? (print 'Majeur') : (print 'Senior'))

Là c’est plus compliqué, non ? Il faut bien prendre le temps de comprendre le code.

Les conditions ternaires sont rares dans d’autres langages, mais sont assez appréciées en Ruby pour leur compacité. Ainsi, nous aurons peut-être l’occasion de les croiser dans un code. En fait, la bonne pratique en Ruby est de privilégier les conditions ternaires si la condition est de la forme if-then-else-end c’est-à-dire dans les cas comme celui que nous avons traité.

Cependant, il faut éviter d’imbriquer plusieurs conditions ternaires et utiliser une structure if ou unless dans ce cas-là. De même, il faut privilégier les structures if et unless si la condition est sur plusieurs lignes.

Exercices

Voilà une nouvelle série d’exercices.

Exercice 1

Le premier exercice est encore une fois plutôt simple : demandons à l’utilisateur d’entrer trois nombres, puis affichons le plus grand des trois nombres.

Correction.

Voici notre correction. Bien sûr, on peut tout à fait avoir un code différent. On a été malin : on compare le nouveau nombre entré au maximum des nombres déjà entrés.

puts 'Entrez trois nombres.'
number = gets.chomp.to_i
max = number
number = gets.chomp.to_i
max = number if number > max
number = gets.chomp.to_i
max = number if numbet > max
print max

Maintenant, modifions ce programme pour qu’il affiche les trois nombres triés par ordre croissant.

Correction.

Pour faire cela, nous allons demander nos trois nombres, les trier, et les afficher ensuite.

puts 'Entrez trois nombres.'
number1 = gets.chomp.to_i
number2 = gets.chomp.to_i
number3 = gets.chomp.to_i 
if number1 > number2 # Si number1 > number2, on les échange.
  tmp = number1
  number1 = number2
  number2 = tmp
end
if number2 > number3 # Si number2 > number3, on les échange.
  tmp = number2
  number2 = number3
  number3 = tmp
  # l'ancien number3 était plus petit que l'ancien number2, alors il peut 
  # être plus petit que number 1.
  if number1 > number2
    tmp = number1
    number1 = number2
    number2 = tmp
  end
end
puts number1, number2, number3

Pour bien comprendre l’idée qui est mise en place, il faut prendre un papier et un crayon.

Exercice 2

Cet exercice est plus un mini TP. Nous devons demander une année à l’utilisateur et déterminer si elle est ou non bissextile. Donc, il nous faut savoir la condition suivant laquelle une année peut être qualifiée de bissextile.

Une année est dite bissextile si elle est divisible par 400 ou si elle est divisible par 4 et non divisible par 100.

Un peu d’aide…

Une question reste problématique : comment tester si un nombre a est multiple d’un nombre b ?
En fait, il suffit de tester le reste de la division entière de b par a. Si ce reste est nul, alors a est un multiple de b.

5 % 2 # 5 n’est pas un multiple de 2.
=> 1
8 % 2 # 8 est un multiple de 2.
=> 0

Il suffit d’appliquer cette méthode à notre cas.

Correction.

Voici une correction possible (d’autres code peuvent aussi fonctionner).

puts 'Entrez une année : '
year = gets.chomp()
year = année.to_i()
if year % 400 == 0
  puts 'L’année est bissextile.'
elsif year % 4 == 0 && year % 100 != 0
  puts 'L’année est bissextile.'
else
  puts 'L’année n’est pas bissextile.'
end

On peut le simplifier.

puts 'Entrez une année : '
année = gets.chomp()
année = année.to_i()
if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
  puts 'L’année est bissextile.'
else
  puts 'L’année n’est pas bissextile.'
end
Exercice 3

Nous allons maintenant devenir commerçants. Notre objectif : demander à l’utilisateur de rentrer le prix HT d’un objet et son code (compris entre 1 et 3), et calculer le prix TTC de l’objet sachant que le code 1 correspond à une TVA de 20 %, le code 2 à une TVA de 10 % et le code 3 à une TVA de 5.5 % (on considère que la seule taxe est la TVA).

Nous allons donc devoir manier des pourcentages.

Correction.

On va déclarer trois constantes correspondant aux trois taux de TVA et les utiliser pour nos calculs.

TVA1 = 20.0
TVA2 = 10.0
TVA3 = 5.5

print 'Entrez le prix de votre produit : '
priceHT = gets.chomp.to_f
print 'Entrez le code de votre produit : '
code = gets.chomp.to_i

if code == 1
  priceTTC = priceHT + TVA1 / 100 * priceHT
elsif code == 2
  priceTTC = priceHT + TVA2 / 100 * priceHT
elsif code == 3
  priceTTC = priceHT + TVA3 / 100 * priceHT
end
  
case code
when 1..3
  print "Le prix TTC de votre produit est #{priceTTC}."   
else
  print 'Votre code n’est pas valide.'
end

Le code peut être raccourci, par exemple de cette manière.

print 'Entrez le prix de votre produit : '
priceHT = gets.chomp.to_f
price 'Entrez le code de votre produit : '
code = gets.chomp.to_i

if code == 1
  print "Le prix TTC de votre produit est #{priceHT * 1.2}."   
elsif code == 2
  print "Le prix TTC de votre produit est #{priceHT * 1.1}." 
elsif code == 3
  print "Le prix TTC de votre produit est #{priceHT * 1.055}."
else
  print 'Votre code n’est pas valide.'
end

Voilà, le chapitre sur les conditions est terminé. Maintenant, nos programmes n’ont plus un comportement linéaire, mais peuvent faire une action suivant la valeur d’une variable.

  • Ruby dispose d’opérateurs de comparaison (supérieur, inférieur, différent, etc.) que l’on peut combiner avec les symboles ||, && et !.
  • Une expression faite avec ces symboles s’appelle une expression booléenne et vaut soit true soit false.
  • On peut exécuter certaines instructions en fonction de la valeur d’un booléen en utilisant des structures conditionnelles (if-elsif-else ou unless-else par exemple).
  • La structure case-when permet de tester une suite de conditions sur une même variable. Elle permet également de tester si une variable appartient à un intervalle donné.
  • Les conditions ternaires sont un moyen condensé d’écrire des conditions et sont assez appréciées en Ruby.