Cours complet pour apprendre à programmer en D


précédentsommairesuivant

33. Boucle foreach

Une des structures de contrôle les plus communes du D est la boucle foreach. Elle est utilisée pour appliquer la même opération à tous les éléments d'un conteneur (ou d'un intervalle).

Les opérations qui sont appliquées aux éléments d'un conteneur sont très répandues en programmation. Nous avons vu dans le chapitre sur la boucle forBoucles for que l'on peut accéder aux éléments d'un tableau dans une boucle for par une valeur d'indice qui est incrémentée à chaque itération :

 
Sélectionnez
for (int i = 0; i != tableau.length; ++i) {
   writeln(tableau[i]);
}

Voici les étapes d'une itération sur tous les éléments :

  • définir une variable compteur, souvent nommée i ;
  • itérer la boucle jusqu'à la valeur de la propriété .length du tableau ;
  • incrémenter i ;
  • accéder à l'élément.

foreach a essentiellement le même comportement, mais simplifie le code en gérant ces étapes automatiquement :

 
Sélectionnez
foreach (element; tableau) {
   writeln(element);
}

Une partie du pouvoir de foreach vient du fait qu'elle peut être utilisée de la même manière indépendamment du type du conteneur. Comme nous l'avons vu dans le chapitre précédent, une manière d'itérer sur les valeurs d'un tableau associatif dans une boucle for est d'utiliser la propriété .values du tableau :

 
Sélectionnez
auto valeurs = aa.values;
for (int i = 0; i != valeurs.length; ++i) {
   writeln(valeurs[i]);
}

foreach ne nécessite rien de particulier pour les tableaux associatifs ; elle est utilisée de la même façon qu'avec les tableaux :

 
Sélectionnez
foreach (valeur; aa) {
   writeln(valeur);
}

33-1. La syntaxe de foreach

foreach consiste en trois sections :

 
Sélectionnez
foreach (noms ; conteneur_ou_intervalle) {
   // opérations
}
  • conteneur_ou_intervalle indique où sont les éléments ;
  • // opérations indique les opérations à appliquer à chaque élément ;
  • noms indique le nom de l'élément et potentiellement d'autres variables dépendant du type du conteneur ou de l'intervalle. Même si le choix de noms appartient au programmeur, le nombre et les types de ces noms dépendent du type du conteneur.

33-2. continue et break

Ces mots-clés ont le même sens que celui qu'ils ont avec la boucle for : continue mène à l'itération suivante au lieu de finir celle qui est en cours et break sort de la boucle.

33-3. foreach avec les tableaux

Quand il y a un seul nom dans la section noms, c'est la valeur de l'élément à chaque itération :

 
Sélectionnez
foreach (element; tableau) {
   writeln(element);
}

Quand deux noms sont indiqués dans la section noms, il y a respectivement un compteur automatique et la valeur de l'élément :

 
Sélectionnez
foreach (i, element; tableau) {
   writeln(i, ": ", element);
}

Le compteur est incrémenté automatiquement par foreach. Son nom est choisi par le programmeur.

33-4. foreach avec les chaînes et std.range.stride

Comme les chaînes sont des tableaux de caractères, foreach fonctionne de la même manière qu'avec les tableaux : un nom unique est le caractère, deux noms sont le compteur et le caractère :

 
Sélectionnez
foreach (c; "salut") {
   writeln(c);
}
 
foreach (i, c; "salut") {
   writeln(i, ": ", c);
}

Cependant, étant des unités de stockage UTF, char et wchar itèrent sur les unités de stockage, pas sur les points de code Unicode :

 
Sélectionnez
foreach (i, code; "abcçd") {
   writeln(i, ": ", code);
}

On accède aux deux unités de stockage UTF-8 qui forment ç par des éléments séparés :

 
Sélectionnez
0: a
1: b
2: c
3:
4: �
5: d

Une manière d'itérer sur les caractères Unicode des chaînes dans une boucle foreach est la fonction stride du module std.range. stride présente la chaîne comme un conteneur constitué de caractères Unicode. Il prend la taille de son pas en second paramètre :

 
Sélectionnez
import std.range;
 
// ...
 
   foreach (c; stride("abcçd", 1)) {
      writeln(c);
   }

Quel que soit le type de caractère de la chaîne, stride présente toujours ses éléments comme des caractères Unicode :

 
Sélectionnez
a
b
c
ç
d

J'expliquerai plus loin pourquoi cette boucle ne peut pas inclure de compteur automatique.

33-5. foreach avec les tableaux associatifs

Un seul nom indique la valeur, deux noms indiquent la clé et la valeur :

 
Sélectionnez
foreach (valeur; ta) {
   writeln(valeur);
}
 
Sélectionnez
foreach (clé, valeur; ta) {
   writeln(clé, ": ", valeur);
}

Les tableaux associatifs peuvent également donner leurs clés et leurs valeurs comme des intervalles. Nous verrons les intervalles dans un chapitre ultérieur. .byKey() et .byValue() retournent des objets intervalle efficaces qui sont aussi utiles dans d'autres contextes. .byValue() n'a pas grand intérêt dans les boucles foreach par rapport aux itérations classiques que l'on a déjà décrites. En revanche, .byKey() est la seule manière efficace d'itérer seulement sur les clés d'un tableau associatif :

 
Sélectionnez
foreach (clé; ta.byKey()) {
   writeln(clé);
}

33-6. foreach avec les intervalles de nombres

Nous avons vu les intervalles de nombres dans le chapitre « Tranches (slices) et autres fonctionnalités des tableaux ». Il est possible d'indiquer un intervalle de nombres dans la section conteneur_ou_intervalle :

 
Sélectionnez
foreach (nombre; 10..15) {
   writeln(nombre);
}

Rappel : 10 est inclus dans l'intervalle, mais pas 15.

33-7. foreach avec les structures, les classes et les intervalles

foreach peut également être utilisé avec des objets d'un type de l'utilisateur qui définit sa propre itération dans les boucles foreach. Comme le type lui-même définit sa propre façon d'itérer, il n'est pas possible de dire grand-chose ici. Les programmeurs doivent se référer à la documentation de ce type particulier.

Les structures et les classes apportent une prise en charge de l'itération foreach soit avec leur méthode opApply(), soit par un ensemble de méthodes d'intervalle. Nous verrons ces fonctionnalités dans des chapitres ultérieurs.

33-8. Le compteur n'est automatique que pour les tableaux

Le compteur automatique est fourni seulement quand on itère sur les tableaux. Quand un compteur est nécessaire lors d'une itération sur d'autres types de conteneurs, le compteur peut être défini et incrémenté de façon explicite :

 
Sélectionnez
int i;
foreach (element; conteneur) {
   // ...
   ++i;
}

Une telle variable est aussi nécessaire quand on compte une condition spécifique. Par exemple, le code suivant compte seulement les valeurs qui sont divisibles par 10 :

 
Sélectionnez
import std.stdio;
 
void main()
{
   auto nombres = [ 1, 0, 15, 10, 3, 5, 20, 30 ];
 
   int compteur;
   foreach (nombre; nombres) {
      if ((nombre % 10) == 0) {
            ++compteur;
            write(compteur);
 
      } else {
            write(' ');
      }
 
      writeln(" : ", nombre);
   }
}

La sortie :

 
Sélectionnez
  : 1
1 : 0
  : 15
2 : 10
  : 3
  : 5
3 : 20
4 : 30

33-9. La copie de l'élément, pas l'élément lui-même

La boucle foreach fournit normalement une copie de l'élément, pas l'élément qui est stocké dans le conteneur. Ceci peut être la cause de bogues.

Pour voir un exemple de ceci, jetons un œil sur le programme suivant qui essaie de doubler les valeurs des éléments d'un tableau :

 
Sélectionnez
import std.stdio;
 
void main()
{
   double[] nombres = [ 1.2, 3.4, 5.6 ];
 
   writefln("Avant : %s", nombres);
 
   foreach (nombre; nombres) {
      nombre *= 2;
   }
 
   writefln("Après : %s", nombres);
}

La sortie du programme montre que l'affectation faite à chaque élément à l'intérieur du corps de foreach n'a aucun effet sur les éléments du conteneur :

 
Sélectionnez
Avant : [1.2, 3.4, 5.6]
Après : [1.2, 3.4, 5.6]

Ceci s'explique par le fait que nombre n'est pas un élément du tableau, mais une copie d'élément. Quand on a besoin de modifier les éléments eux-mêmes, le nom doit être défini comme une référence à l'élément par le mot-clé ref :

 
Sélectionnez
foreach (ref nombre; nombres) {
   nombre *= 2;
}

La nouvelle sortie montre que maintenant, les affectations modifient les éléments du tableau :

 
Sélectionnez
Avant : [1.2, 3.4, 5.6]
Après : [2.4, 6.8, 11.2]

Le mot-clé ref fait de nombre un alias de l'élément à chaque itération. De ce fait, les modifications apportées à nombre sont apportées à cet élément du conteneur.

33-10. L'intégrité du conteneur doit être préservée

Même s'il est correct de modifier les éléments du conteneur à travers des variables ref, la structure du conteneur ne doit pas changer. Par exemple, les éléments ne doivent pas être supprimés ou ajoutés au conteneur pendant une boucle foreach.

De telles modifications peuvent perturber le fonctionnement interne de l'itération de la boucle et mettre le programme dans un état incohérent.

33-11. foreach_reverse pour itérer dans la direction inverse

foreach_reverse fonctionne de la même manière que foreach, mais itère dans la direction inverse :

 
Sélectionnez
auto conteneur = [ 1, 2, 3 ];
 
foreach_reverse (element; conteneur) {
    writefln("%s ", element);
}

La sortie :

 
Sélectionnez
3
2
1

L'utilisation de foreach_reverse n'est pas répandue parce que la fonction d'intervalle retro() fait la même chose. Nous verrons cette fonction dans un chapitre suivant.

33-12. Exercice

Nous savons que les tableaux associatifs proposent une relation de type clé-valeur. Cette relation est unidirectionnelle : on accède aux valeurs par les clés, mais ce n'est pas vrai dans l'autre sens.

Supposons que l'on ait ce tableau associatif :

 
Sélectionnez
string[int] noms = [ 1:"un", 7:"sept", 20:"vingt" ];

Utilisez ce tableau associatif et une boucle foreach pour remplir un tableau associatif nommé valeurs. Ce nouveau tableau associatif doit donner les valeurs qui correspondent aux noms. Par exemple, la ligne suivante devrait afficher 20 :

 
Sélectionnez
writeln(valeurs["vingt"]);

La solution.La boucle foreach - Correction


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+