3 septembre 2023

Boucles : while et for

Nous avons souvent besoin d’effectuer des actions similaires plusieurs fois de suite.

Par exemple, lorsque nous devons extraire des marchandises d’une liste les unes à la suite des autres. Ou exécutez simplement le même code pour chaque numéro de 1 à 10.

Les boucles permettent de répéter plusieurs fois la même partie du code.

Les boucles for…of et for…in

Une petite annonce pour les lecteurs avertis.

Cet article ne couvre que les boucles de base : while, do..while et for(..;..;..).

Si vous êtes venu à cet article à la recherche d’autres types de boucles, voici les pointeurs :

  • Voir for…in pour boucler sur les propriétés de l’objet.
  • Voir for…of et iterables pour boucler sur des tableaux et des objets itérables.

Sinon, lisez la suite.

La boucle “while”

La boucle while a la syntaxe suivante :

while (condition) {
  // code
  // appelé "loop body" ("corps de boucle")
}

Tant que la condition est vraie, le code du corps de la boucle est exécuté.

Par exemple, la boucle ci-dessous affiche i tant que i < 3 :

let i = 0;
while (i < 3) { // affiche 0, puis 1, puis 2
  alert( i );
  i++;
}

Une unique exécution du corps de la boucle est appelée une itération. La boucle dans l’exemple ci-dessus fait trois itérations.

S’il n’y avait pas d’i++ dans l’exemple ci-dessus, la boucle se répèterait (en théorie) pour toujours. En pratique, le navigateur fournit des moyens d’arrêter ces boucles, et pour JavaScript côté serveur, nous pouvons tuer le processus.

Toute expression ou variable peut être une condition de boucle, pas seulement une comparaison. Ils sont évalués et convertis en un booléen par while.

Par exemple, le moyen le plus court d’écrire while (i != 0) pourrait être while (i) :

let i = 3;
while (i) { // quand i devient 0, la condition devient fausse et la boucle s'arrête
  alert( i );
  i--;
}
Les accolades ne sont pas requis pour un corps à une seule ligne

Si le corps de la boucle a une seule déclaration, nous pouvons omettre les accolades {…} :

let i = 3;
while (i) alert(i--);

La boucle “do…while”

La vérification de la condition peut être déplacée sous le corps de la boucle en utilisant la syntaxe do..while :

do {
  // corps de la boucle
} while (condition);

La boucle exécute d’abord le corps, puis vérifie la condition et, tant que c’est vrai, l’exécute encore et encore.

Par exemple :

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

Cette forme de syntaxe est rarement utilisée, sauf lorsque vous souhaitez que le corps de la boucle s’exécute au moins une fois, quelle que soit la condition. Habituellement, l’autre forme est préférée : while(…) {…}.

La boucle “for”

La boucle for est plus complexe, mais c’est aussi la boucle la plus utilisée.

Cela ressemble à ceci :

for (début; condition; étape) {
  // ... corps de la boucle ...
}

Apprenons la signification de ces parties par l’exemple. La boucle ci-dessous exécute alert(i) pour i en partant de 0 jusqu’à 3 (mais non compris) :

for (let i = 0; i < 3; i++) { // affiche 0, puis 1, puis 2
  alert(i);
}

Examinons la déclaration for partie par partie :

partie
début let i = 0 Exécute une fois en entrant dans la boucle.
condition i < 3 Vérifié avant chaque itération de la boucle, en cas d’échec, la boucle s’arrête.
corps alert(i) Exécute encore et encore tant que la condition est vraie
étape i++ Exécute après le corps à chaque itération

L’algorithme de boucle général fonctionne comme ceci :

Exécuter le début
→ (si condition → exécuter le corps et exécuter l'étape)
→ (si condition → exécuter le corps et exécuter l'étape)
→ (si condition → exécuter le corps et exécuter l'étape)
→ ...

C’est-à-dire que begin est exécuté une fois, puis itéré : après chaque test de condition, body et step sont exécutés.

Si vous débutez dans les boucles, il pourrait être utile de revenir à l’exemple et de reproduire comment elle s’exécute pas à pas sur une feuille de papier.

Voici ce qui se passe exactement dans notre cas :

// for (let i = 0; i < 3; i++) alert(i)

// exécute début
let i = 0
// si condition → exécuter le corps et exécuter l'étape
if (i < 3) { alert(i); i++ }
// si condition → exécuter le corps et exécuter l'étape
if (i < 3) { alert(i); i++ }
// si condition → exécuter le corps et exécuter l'étape
if (i < 3) { alert(i); i++ }
// ... fini, parce que maintenant i == 3
Déclaration de variable en ligne

Ici, la variable “counter” i est déclarée directement dans la boucle. Cela s’appelle une déclaration de variable “en ligne”. De telles variables ne sont visibles que dans la boucle.

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // erreur, pas de variable

Au lieu de définir une variable, nous pouvons en utiliser une existante :

let i = 0;

for (i = 0; i < 3; i++) { // utiliser une variable existante
  alert(i); // 0, 1, 2
}

alert(i); // 3, visible, car déclaré en dehors de la boucle

Sauter des parties

Toute partie de for peut être ignorée.

Par exemple, nous pouvons omettre le début si nous n’avons rien à faire au début de la boucle.

Comme ici :

let i = 0; // nous avons i déjà déclaré et assigné

for (; i < 3; i++) { // pas besoin de "début"
  alert( i ); // 0, 1, 2
}

Nous pouvons également supprimer la partie étape :

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

La boucle est devenue identique à while (i < 3).

Nous pouvons tout supprimer, créant ainsi une boucle infinie :

for (;;) {
  // répète sans limites
}

Veuillez noter que les deux les points-virgules ; de for doivent être présents, sinon ce serait une erreur de syntaxe.

Briser la boucle

Normalement, la boucle sort quand la condition devient fausse.

Mais nous pouvons forcer la sortie à tout moment. Il y a une directive spéciale appelée break pour cela.

Par exemple, la boucle ci-dessous demande à l’utilisateur une série de chiffres, mais “se casse” quand aucun numéro n’est entré :

let sum = 0;

while (true) {

  let value = +prompt("Entrez un nombre", '');

  if (!value) break; // (*)

  sum += value;

}
alert( 'Sum: ' + sum );

La directive break est activée sur la ligne (*) si l’utilisateur entre une ligne vide ou annule l’entrée. Il arrête la boucle immédiatement, en passant le contrôle à la première ligne après la boucle. À savoir, alert.

La combinaison “boucle infinie + break au besoin” est idéale pour les situations où la condition doit être vérifiée non pas au début / à la fin de la boucle, mais au milieu, voire à plusieurs endroits du corps.

Continuer jusqu'à la prochaine itération

La directive continue est une “version plus légère” de break. Cela n’arrête pas toute la boucle. Au lieu de cela, elle arrête l’itération en cours et force la boucle à en démarrer une nouvelle (si la condition le permet).

Nous pouvons l’utiliser si nous avons terminé l’itération en cours et aimerions passer à la suivante.

La boucle ci-dessous utilise continue pour ne produire que des valeurs impaires :

for (let i = 0; i < 10; i++) {

  // si vrai, saute le reste du corps
  if (i % 2 == 0) continue;

  alert(i); // 1, ensuite 3, 5, 7, 9
}

Pour les valeurs paires de i, la directive continue arrête l’exécution du corps en passant le contrôle à la prochaine itération de for (avec le nombre suivant). Donc, l’alert n’est appelée que pour les valeurs impaires.

La directive continue aide à réduire le niveau d’imbrication

Une boucle affichant des valeurs impaires pourrait ressembler à ceci :

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

D’un point de vue technique, c’est identique à l’exemple du dessus. Certes, nous pouvons simplement envelopper le code dans un bloc if au lieu de continue.

Mais comme effet secondaire, nous avons obtenu un niveau d’imbrication supplémentaire (l’appel de l’alert à l’intérieur des accolades). Si le code à l’intérieur du if est plus long que quelques lignes, la lisibilité globale peut en être réduite.

Pas de break/continue à droite de ‘?’

Veuillez noter que les constructions de syntaxe qui ne sont pas des expressions ne peuvent pas être utilisées avec l’opérateur ternaire ?. Tout particulièrement les directives telles que break/continue ne sont pas autorisées.

Par exemple, si nous prenons ce code :

if (i > 5) {
  alert(i);
} else {
  continue;
}

… Et le réécrivons à l’aide d’un point d’interrogation :

(i > 5) ? alert(i) : continue; // continue n'est pas autorisé ici

… Ensuite cesse de fonctionner : il y a une erreur de syntaxe.

C’est une autre raison pour ne pas utiliser l’opérateur point d’interrogation ? au lieu de if.

Des labels pour break/continue

Parfois, nous devons sortir de plusieurs boucles imbriquées en même temps.

Par exemple, dans le code ci-dessous, nous bouclons sur i et j pour demander les coordonnées (i, j) de (0,0) à (2,2) :

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // Et si nous voulons sortir d'ici à Done (ci-dessous) ?
  }
}

alert('Done!');

Nous avons besoin d’un moyen d’arrêter le processus si l’utilisateur annule la saisie.

Le break ordinaire après input ne ferait que briser la boucle intérieure. Ce n’est pas suffisant – les labels viennent à la rescousse.

Une label est un identifiant avec deux points avant une boucle :

labelName: for (...) {
  ...
}

L’instruction break <labelName> dans la boucle interrompt tout le bloc de code relatif au label.

Comme ici :

outer: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // si une chaîne est vide ou annulée, alors rompre les deux boucles
    if (!input) break outer; // (*)

    // faire quelque chose avec la valeur …
  }
}

alert('Done!');

Dans le code ci-dessus, break outer regarde vers le haut le label outer et sort de cette boucle.

Donc, le contrôle va directement de (*) à alert('Done!').

Nous pouvons également déplacer le label sur une ligne séparée :

outer:
for (let i = 0; i < 3; i++) { ... }

La directive continue peut également être utilisée avec un label. Dans ce cas, l’exécution passe à l’itération suivante de la boucle labellisée.

Les labels ne permettent pas de “sauter” n’importe où

Les labels ne nous permettent pas de sauter dans un endroit arbitraire du code.

Par exemple, il est impossible de faire ceci :

break label;  // saute au label ci-dessous (ne fonctionne pas)

label: for (...)

Une directive break doit être à l’intérieur d’un bloc de code. Techniquement, tout bloc de code étiqueté fera l’affaire, par exemple :

label: {
  // ...
  break label; // works
  // ...
}

… Bien que 99,9% du temps les break utilisés sont à l’intérieur de boucles, comme nous l’avons vu dans les exemples ci-dessus.

Un continue n’est possible que depuis l’intérieur d’une boucle.

Résumé

Nous avons couvert 3 types de boucles :

  • while – La condition est vérifiée avant chaque itération.
  • do..while – La condition est vérifiée après chaque itération.
  • for (;;) – La condition est vérifiée avant chaque itération, des paramètres supplémentaires sont disponibles.

Pour créer une boucle “infinie”, on utilise généralement la construction while(true). Une telle boucle, comme toute autre, peut être stoppée avec la directive break.

Si nous ne voulons rien faire avec l’itération actuelle et que nous souhaitons avancer jusqu’à la suivante, la directive continue nous permet de faire cela.

break/continue accepte les labels précédents la boucle. Un label est le seul moyen de break/continue pour échapper à l’imbrication et accéder en dehors de la boucle.

Exercices

importance: 3

Quelle est la dernière valeur affichée par ce code ? Pourquoi ?

let i = 3;

while (i) {
  alert( i-- );
}

La réponse : 1.

let i = 3;

while (i) {
  alert( i-- );
}

Chaque itération de boucle diminue i de 1. La vérification while(i) arrête la boucle lorsque i = 0.

Par conséquent, les étapes de la boucle forment la séquence suivante (“boucle décomposée”) :

let i = 3;

alert(i--); // affiche 3, diminue i à 2

alert(i--) // affiche 2, diminue i à 1

alert(i--) // affiche 1, diminue i à 0

// terminé, la vérification while(i) termine la boucle
importance: 4

A votre avis, quelles sont les valeurs affichées pour chaque boucle ? Notez-les puis comparer avec la réponse.

Les deux boucles affichent-elles les mêmes valeurs dans l’alert ou pas ?

  1. Le préfixe sous forme ++i :

    let i = 0;
    while (++i < 5) alert( i );
  2. Le postfixe sous forme i++ :

    let i = 0;
    while (i++ < 5) alert( i );

L’exercice montre comment les formes postfix/prefix peuvent conduire à des résultats différents lorsqu’ils sont utilisés dans des comparaisons.

  1. De 1 à 4

    let i = 0;
    while (++i < 5) alert( i );

    La première valeur est i=1, parce que ++i incrémente d’abord i puis renvoie la nouvelle valeur. La première comparaison est donc 1 < 5 et alert indique 1.

    Ensuite, viennent 2,3,4… – les valeurs apparaissent les unes après les autres. La comparaison utilise toujours la valeur incrémentée, car ++ est avant la variable.

    Enfin, i=4 est incrémenté à 5, la comparaison while(5 < 5) échoue et la boucle s’arrête. Donc 5 n’est pas affiché.

  2. De 1 à 5

    let i = 0;
    while (i++ < 5) alert( i );

    La première valeur est encore i=1. La forme postfixée de i++ incrémente i puis renvoie l’ancienne valeur, la comparaison i++ < 5 utilisera donc i=0 (contrairement à ++i < 5).

    Mais l’appel d’alert est séparé. C’est une autre instruction qui s’exécute après l’incrémentation et la comparaison. Donc, on obtient i=1.

    Ensuite viennent 2,3,4…

    Arrêtons-nous sur i=4. Le préfixe sous forme ++i l’incrémenterait et utiliserait 5 dans la comparaison. Mais ici nous avons la forme postfixée i++. Donc, i augmente à 5, mais renvoie l’ancienne valeur. Par conséquent, la comparaison est en réalité while(4 < 5) – true, et le contrôle continue à alert.

    La valeur i=5 est la dernière, car à l’étape suivante while(5 <5) est faux.

importance: 4

Pour chaque boucle, notez les valeurs qui vont s’afficher. Ensuite, comparez avec la réponse.

Les deux boucles alert les mêmes valeurs ou pas ?

  1. La forme postfix :

    for (let i = 0; i < 5; i++) alert( i );
  2. La forme préfix :

    for (let i = 0; i < 5; ++i) alert( i );

La réponse: de 0 à 4 dans les deux cas.

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

Cela peut être facilement déduit de l’algorithme de for :

  1. Exécute une fois i = 0 avant tout (début).
  2. Vérifie l’état i < 5
  3. Si true – execute le corps de la boucle alert(i), et ensuite i++

L’incrément i++ est séparé de la vérification de condition (2). C’est juste une autre déclaration.

La valeur renvoyée par l’incrémentation n’est pas utilisée ici, il n’y a donc pas de différence entre i++ et ++i.

importance: 5

Utilisez la boucle for pour afficher les nombres pairs de 2 à 10.

Exécuter la démo

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

Ici nous utilisons l’opérateur “modulo” % pour obtenir le reste et vérifier si c’est pair ou pas.

importance: 5

Réécrivez le code en modifiant la boucle for en while sans modifier son comportement (la sortie doit rester la même).

for (let i = 0; i < 3; i++) {
  alert( `number ${i}!` );
}
let i = 0;
while (i < 3) {
  alert( `number ${i}!` );
  i++;
}
importance: 5

Ecrivez une boucle qui demande un nombre supérieur à 100. Si le visiteur saisit un autre numéro, demandez-lui de le saisir à nouveau.

La boucle doit demander un numéro jusqu’à ce que le visiteur saisisse un nombre supérieur à 100 ou annule l’entrée/entre une ligne vide.

Ici, nous pouvons supposer que le visiteur ne saisit que des chiffres. Il n’est pas nécessaire de mettre en œuvre un traitement spécial pour une entrée non numérique dans cette tâche.

Exécuter la démo

let num;

do {
  num = prompt("Enter a number greater than 100?", 0);
} while (num <= 100 && num);

La boucle do..while se répète tant que les deux vérifications sont vrai :

  1. La vérification de num <= 100 – c’est-à-dire que la valeur entrée n’est toujours pas supérieure à 100.
  2. La vérification que && num est false lorsque num est null ou une chaîne vide. Ensuite, la boucle while s’arrête aussi.

P.S. Si num est null, alors num <= 100 est true. Par conséquent, sans la seconde vérification, la boucle ne s’arrêterait pas si l’utilisateur cliquait sur CANCEL. Les deux vérifications sont obligatoires.

importance: 3

Un nombre entier supérieur à 1 est appelé un Nombre premier s’il ne peut être divisé sans reste par rien d’autre que 1 et lui-même.

En d’autres termes, n > 1 est un nombre premier s’il ne peut être divisé de manière égale par autre chose que 1 et n.

Par exemple, 5 est un nombre premier, car il ne peut pas être divisé sans reste par 2, 3 et 4.

Écrivez un code qui produit les nombres premiers dans l’intervall e 2 à n.

Pour n = 10, le résultat sera 2,3,5,7.

P.S. Le code devrait fonctionner pour n’importe quel n et aucune valeur fixe ne doit être codé en dur.

Il existe de nombreux algorithmes pour cette tâche.

Utilisons une boucle imbriquée :

Pour chaque i dans l'intervalle {
  vérifier si i a un diviseur de 1..i
  si oui => la valeur n'est pas un nombre premier
  si non => la valeur est un nombre premier, affichez-le
}

Un code utilisant un label :

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // Pour chaque i...

  for (let j = 2; j < i; j++) { // cherche un diviseur ..
    if (i % j == 0) continue nextPrime; // pas un premier, on passe au prochain i
  }

  alert( i ); // un premier
}

Il y a beaucoup d’espace pour l’optimiser. Par exemple, nous pourrions rechercher les diviseurs de 2 à la racine carrée de i. Quoi qu’il en soit, si nous voulons être vraiment efficaces pour les grands intervalles, nous devons changer d’approche et nous baser sur des mathématiques avancées et des algorithmes complexes comme Crible quadratique, Crible algébrique etc.

Carte du tutoriel