5 - Le développement 5.4 - La programmation

Chapitre 5.4.5Les boucles

Exécute ceci tant que cela est vrai.
Temps de lecture : 5 minutes

Les boucles permettent d'exécuter plusieurs fois le même code. Sans elles, il faudrait copier-coller chaque instruction autant de fois que nécessaire.

Tant que (while)

La boucle while exécute un bloc de code tant qu'une condition est vraie :

Dans la console de ton navigateur
let compteur = 1

while (compteur <= 5) {
    console.log("Tour numéro " + compteur)
    compteur = compteur + 1
}
// Affiche : Tour numéro 1, Tour numéro 2, ... Tour numéro 5

À chaque tour, on incrémente le compteur de 1. Quand il atteint 6, la condition compteur <= 5 devient fausse et la boucle s'arrête.

Pour (for)

La boucle for est une version plus compacte de while quand on connaît à l'avance le nombre d'itérations. Elle regroupe la déclaration du compteur, la condition d'arrêt et l'incrémentation sur une seule ligne :

Boucle for
for (let compteur = 1; compteur <= 5; compteur++) {
    console.log("Tour " + compteur)
}
// Affiche : Tour 1, ... Tour 5

compteur++ est un raccourci pour compteur = compteur + 1.

Pour chaque (forEach)

Quand on veut parcourir les éléments d'un tableau un par un, on utilise forEach :

Parcourir un tableau
let courses = ["Pommes", "Pain", "Lait"]

courses.forEach(function(article) {
    console.log("Acheter : " + article)
})

Pour chaque élément du tableau, la fonction passée en paramètre est exécutée. C'est le callback dont on parlera dans le chapitre sur les fonctions. Le forEach est très courant dans le développement web : parcourir une liste de clients, d'articles, de commandes...

Exemple :

Un site e-commerce veut envoyer un email de confirmation à tous les clients qui ont passé commande aujourd'hui. On récupère la liste des commandes du jour, et pour chaque commande, on envoie un email au client.

La boucle infinie

Si la condition d'arrêt n'est jamais atteinte, la boucle tourne indéfiniment. Le programme se bloque, consomme toutes les ressources de la machine, et il faut le forcer à s'arrêter.

À ne pas faire
// DANGER : boucle infinie !
let compteur = 1
while (compteur > 0) {
    compteur = compteur + 1
}
// compteur ne sera jamais <= 0, la boucle ne s'arrête jamais

N'exécute pas cet exemple dans ta console, ça bloquerait ton onglet. La boucle infinie est un bug fréquent. L'essentiel, c'est de toujours s'assurer que la condition d'arrêt finira par être atteinte.

Sortir d'une boucle : break et continue

La condition d'arrêt n'est pas le seul moyen de reprendre la main. Deux mots-clés permettent d'intervenir en plein milieu d'un tour.

break

Le break arrête la boucle immédiatement et passe à la suite du programme. On l'utilise par exemple pour une recherche, dès qu'on a trouvé ce qu'on cherchait, il n'y a plus rien à parcourir.

Prenons un client qui saisit un code promo au moment de payer. Il faut vérifier qu'il figure bien dans la liste des codes valides.

Chercher un code promo
let codesValides = ["BIENVENUE10", "NOEL20", "ETE15", "BLACKFRIDAY30"]
let codeSaisi = "ETE15"
let codeAccepte = false

for (let i = 0; i < codesValides.length; i++) {
    if (codesValides[i] === codeSaisi) {
        codeAccepte = true
        break  // trouvé, inutile de regarder les suivants
    }
}

console.log(codeAccepte)  // true

Sans le break, le programme continuerait à comparer le code aux suivants alors que la réponse est déjà connue. Sur quatre codes personne ne le remarque, sur un catalogue de plusieurs centaines de milliers de références la différence devient très concrète.

continue

Le continue abandonne le tour en cours et passe directement au suivant, sans arrêter la boucle. Il sert à écarter les cas qui n'ont rien à faire dans le traitement.

Imaginons la note moyenne d'un produit, sachant que tous les acheteurs n'ont pas laissé d'avis.

Calculer une note moyenne
let notes = [5, null, 4, 3, null, 5]
let total = 0
let nombreDAvis = 0

for (let i = 0; i < notes.length; i++) {
    if (notes[i] === null) {
        continue  // cet acheteur n'a pas laissé d'avis, il ne compte pas
    }
    total = total + notes[i]
    nombreDAvis = nombreDAvis + 1
}

console.log(total / nombreDAvis)  // 4.25

Map, filter et reduce, une autre façon de penser

Tout ce qu'on vient de voir décrit comment parcourir une liste, avec un compteur, une condition d'arrêt et des instructions à répéter. Il existe une autre approche, où l'on décrit plutôt le résultat attendu et où la mécanique du parcours devient invisible. Elle repose sur trois opérations :

  1. map : transforme chaque élément de la liste, indépendamment des autres, et produit une liste de même taille.
  2. filter : ne garde que les éléments qui remplissent une condition, et produit donc une liste plus courte.
  3. reduce : condense toute la liste en une seule valeur, un total, une moyenne, un maximum.

Prenons une opération commerciale, une remise de 10 % sur les articles à plus de 10 euros, et calculons le montant remisé. Les trois opérations s'enchaînent, chacune travaillant sur le résultat de la précédente.

map, filter et reduce
let panier = [12, 30, 8]

// filter : seuls les articles à plus de 10 euros sont concernés
let eligibles = panier.filter(function(prix) {
    return prix > 10
})
console.log(eligibles)  // [12, 30]

// map : on applique la remise de 10% à chacun d'eux
let remises = eligibles.map(function(prix) {
    return prix * 0.9
})
console.log(remises)  // [10.8, 27]

// reduce : on additionne pour obtenir le montant remisé
let total = remises.reduce(function(somme, prix) {
    return somme + prix
}, 0)
console.log(total)  // 37.8

Tout ça, tu pourrais l'écrire avec un for et un compteur, le résultat serait identique. L'intérêt est ailleurs. Le nom de l'opération raconte l'intention, là où une boucle t'oblige à lire tout le bloc pour la deviner.

Mais la vraie portée de ces notions dépasse largement le confort de lecture. Puisqu'un map traite chaque élément sans se soucier des autres, rien n'oblige à les traiter l'un après l'autre, ni même sur la même machine. On peut découper la liste en mille morceaux, les distribuer à mille ordinateurs, puis rassembler les résultats avec un reduce. C'est exactement le modèle que Google a formalisé en 2004 sous le nom de MapReduce pour indexer le web, et qui a donné naissance à tout l'écosystème du big data.

Une boucle classique, elle, est prisonnière de son ordre. Le tour numéro 2 suit le tour numéro 1, sur un seul processeur. C'est toute la différence entre décrire une intention, qu'une machine peut réorganiser comme elle veut, et dicter une marche à suivre, qu'elle doit exécuter au pied de la lettre.

Boucles et conditions : la base des algorithmes

Chaque langage propose ses propres variantes de boucles, avec des noms et des syntaxes différents : for...of en JavaScript, foreach en PHP, les compréhensions de liste en Python, les Stream en Java... Ne t'inquiète pas de tous les retenir : ce sont toujours les mêmes concepts (parcourir, transformer, s'arrêter) habillés différemment.

Maintenant que tu comprends les concepts de boucles et de conditions, tu vas commencer à les voir partout dans ta vie quotidienne. Quand l'équipe marketing demandera "envoie un email de rappel à tous les clients dont l'abonnement expire ce mois-ci", tu reconnaîtras intuitivement une boucle et une condition.

À elles deux, ces notions suffisent déjà à écrire la plupart des algorithmes. C'est tout l'intérêt de ces deux chapitres, la traduction en code n'est plus qu'une formalité de syntaxe. Le vrai travail consiste à découper un besoin en une suite d'étapes sans ambiguïté.

Reste une question : comment éviter que ces étapes ne s'empilent en un bloc illisible ? C'est le rôle des fonctions, le sujet du chapitre suivant.