programmez-en-d/foreach.whata

347 lines
11 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Boucle [c foreach]"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Une des structures de contrôle les plus communes du D est la boucle [c 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 [[part:for | chapitre sur la boucle [c for]] que l'on peut accéder aux éléments d'un tableau dans une boucle [c for] par une valeur d'indice qui est incrémentée à chaque itération~ :
[code=d <<<
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 [c i]~ ;
- itérer la boucle jusqu'à la valeur de la propriété [c .length] du tableau~ ;
- incrémenter [c i]~ ;
- accéder à l'élément.
[c foreach] a essentiellement le même comportement mais simplifie le code en gérant ces étapes automatiquement~ :
[code=d <<<
foreach (element; tableau) {
writeln(element);
}
>>>]
Une partie du pouvoir de [c 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é [c .values] du tableau~ :
[code=d <<<
auto valeurs = aa.values;
for (int i = 0; i != valeurs.length; ++i) {
writeln(valeurs[i]);
}
>>>]
[c 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~ :
[code=d <<<
foreach (valeur; aa) {
writeln(valeur);
}
>>>]
[ = La syntaxe de [c foreach]
[c foreach] consiste en trois sections~ :
[code=d <<<
foreach (noms ; conteneur_ou_intervalle) {
// opérations
}
>>>]
- [c conteneur_ou_intervalle] indique où sont les éléments~ ;
- [c // opérations] indique les opérations à appliquer à chaque élément~ ;
- [c 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 [c noms] appartient au programmeur, le nombre et les types de ces noms dépend du type du conteneur.
]
[ = [c continue] et [c break]
Ces mots-clés ont le même sens que celui qu'ils ont avec la boucle [c for]~ : [c continue] mène à l'itération suivante au lieu de finir celle qui est en cours et [c break] sort de la boucle.
]
[ = [c foreach] avec les tableaux
Quand il y a un seul nom dans la section [c noms], c'est la valeur de l'élément à chaque itération~ :
[code=d <<<
foreach (element; tableau) {
writeln(element);
}
>>>]
Quand deux noms sont indiqués dans la section [c noms], il y a respectivement un compteur automatique et la valeur de l'élément~ :
[code=d <<<
foreach (i, element; tableau) {
writeln(i, ": ", element);
}
>>>]
Le compteur est incrémenté automatiquement par [c foreach]. Son nom est choisi par le programmeur.
]
[ = [c foreach] avec les chaînes et [c std.range.stride]
Comme les chaînes sont des tableaux de caractères, [c 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~ :
[code=d <<<
foreach (c; "salut") {
writeln(c);
}
foreach (i, c; "salut") {
writeln(i, ": ", c);
}
>>>]
Cependant, étant des unités de stockage UTF, [c char] et [c wchar] itèrent sur les unités de stockage, par sur les points de code Unicode~ :
[code=d <<<
foreach (i, code; "abcçd") {
writeln(i, ": ", code);
}
>>>]
On accède aux deux unités de stockage UTF-8 qui forment [c ç] par des éléments séparés~ :
[output <<<
0: a
1: b
2: c
3:
4: <20>
5: d
>>>]
Une manière d'itérer sur les caractères Unicode des chaînes dans une boucle [c foreach] est la fonction [c stride] du module [c std.range]. [c 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~ :
[code=d <<<
import std.range;
// ...
foreach (c; stride("abcçd", 1)) {
writeln(c);
}
>>>]
Quel que soit le type de caractère de la chaîne, [c stride] présente toujours ses éléments comme des caractères Unicode~ :
[output <<<
a
b
c
ç
d
>>>]
J'expliquerai plus loin pourquoi cette boucle ne peut pas inclure de compteur automatique.
]
[ = [c foreach] avec les tableaux associatifs
Un seul nom indique la valeur, deux noms indiquent la clé et la valeur~ :
[code=d <<<
foreach (valeur; ta) {
writeln(valeur);
}
>>>]
[code=d <<<
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. [c .byKey()] et [c .byValue()] retournent des objets intervalle efficaces qui sont aussi utiles dans d'autres contextes. [c .byValue()] n'a pas grand intérêt dans les boucles [c foreach] par rapport aux itérations classiques que l'on a déjà décrites. En revanche, [c .byKey()] est la seule manière efficace d'itérer seulement sur les clés d'un tableau associatif~ :
[code=d <<<
foreach (clé; ta.byKey()) {
writeln(clé);
}
>>>]
]
[ = [c foreach] avec les intervalles de nombres
Nous avons vu les intervalles de nombres dans le chapitre [[part:tranches | «~ Tranches (slices) et autres fonctionnalités des tableaux~ »]. Il est possible d'indiquer un intervalle de nombre dans la section [c conteneur_ou_intervalle]~ :
[code=d <<<
foreach (nombre; 10..15) {
writeln(nombre);
}
>>>]
[p Rappel~ : | 10 est inclus dans l'intervalle mais pas 15.]
]
[ = [c foreach] avec les structures, les classes et les intervalles
[c foreach] peut également être utilisé avec des objets d'un type de l'utilisateur qui définit sa propre itération dans les boucles [c 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 [c foreach] soit avec leur méthode [c opApply()], soit par un ensemble de méthodes d'intervalle. Nous verrons ces fonctionnalités dans des chapitres ultérieurs.
]
[ = 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~ :
[code=d <<<
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~ :
[code=d <<<
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 :
[output <<<
: 1
1 : 0
: 15
2 : 10
: 3
: 5
3 : 20
4 : 30
>>>]
]
[ = La copie de l'élément, pas l'élément lui-même
La boucle [c 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ément d'un tableau :
[code=d <<<
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 [c foreach] n'a aucun effet sur les éléments du conteneur~ :
[code=d <<<
Avant : [1.2, 3.4, 5.6]
Après : [1.2, 3.4, 5.6]
>>>]
Ceci s'explique par le fait que [c 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ême, le nom doit être défini comme une référence à l'élément par le mot-clé [c ref]~ :
[code=d <<<
foreach (ref nombre; nombres) {
nombre *= 2;
}
>>>]
La nouvelle sortie montre que maintenant, les affectations modifient les éléments du tableau~ :
[code=d <<<
Avant : [1.2, 3.4, 5.6]
Après : [2.4, 6.8, 11.2]
>>>]
Le mot-clé [c ref] fait de [c nombre] un alias de l'élément à chaque itération. De ce fait, les modifications apportées à [c nombre] sont apportées à cet élément du conteneur.
]
[ = 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 [c 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 [c 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.
]
[ = ``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~ :
[code=d <<<
auto conteneur = [ 1, 2, 3 ];
foreach_reverse (element; conteneur) {
writefln("%s ", element);
}
>>>]
La sortie~ :
[output <<<
3
2
1
>>>]
L'utilisation de [c 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.
]
[ = Exercice
Nous savons que les tableaux associatifs proposent une relation de type clés-valeurs. 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~ :
[code=d <<<
string[int] noms = [ 1:"un", 7:"sept", 20:"vingt" ];
>>>]
Utilisez ce tableau associatif et une boucle [c foreach] pour remplir un tableau associatif nommé [c valeurs]. Ce nouveau tableau associatif doit donner les valeurs qui correspondent aux noms. Par exemple, la ligne suivante devrait afficher ``20``~ :
[code=d <<<
writeln(valeurs["vingt"]);
>>>]
[[part:corrections/foreach | … La solution]]
]