Sign in Join
Wiki copytemporary

for-in loop/fr: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: for-in loop/fr

OldNewLine
1+La construction de boucle "for-in" est prise en charge dans Delphi depuis Delphi 2005. Elle a été mise en œuvre dans FPC 2.4.2.
2+
3+## Contents
4+
5+ * 1 Implémentation Delphi et FPC
6+ * 1.1 Boucle sur une chaîne
7+ * 1.2 Boucle sur un tableau
8+ * 1.3 Boucle sur un ensemble
9+ * 1.4 Traverser un conteneur
10+ * 1.5 Enumérateurs multiples pour une classe
11+ * 2 Extensions FPC
12+ * 2.1 Traversée de types énumération et intervalle
13+ * 2.2 Déclarer des énumérateurs
14+ * 2.2.1 Traversée de chaînes UTF-8
15+ * 2.3 Utiliser des identificateurs au lieu de MoveNext et Current
16+ * 3 Extensions proposées
17+ * 3.1 Donner la position de l'énumérateur si disponible
18+ * 3.1.1 Problématique
19+ * 3.1.2 Solution de contournement pour l'indice
20+ * 4 Référence
21+
22+# Implémentation Delphi et FPC
23+
24+Une boucle **for in** respecte la syntaxe suivante:
25+
26+## Boucle sur une chaîne
27+```
28+procedure StringLoop(S: String);
29+ var
30+ C: Char;
31+ begin
32+ for C in S do
33+ DoSomething(C);
34+ end;
35+```
36+
37+## Boucle sur un tableau
38+```
39+procedure ArrayLoop(A: Array of Byte);
40+ var
41+ B: Byte;
42+ begin
43+ for B in A do
44+ DoSomething(B);
45+ end;
46+```
47+
48+## Boucle sur un ensemble
49+```
50+type
51+ TColor = (cRed, cGren, cBlue);
52+ TColors = set of TColor;
53+ procedure SetLoop(Colors: TColors);
54+ var
55+ Color: TColor;
56+ begin
57+ for Color in Colors do
58+ DoSomething(Color);
59+ end;
60+```
61+
62+## Traverser un conteneur
63+
64+Pour travrser une classe conteneur, vous avez besoin d'ajouter un **énumérateur**. Un **Enumérateur** est une classe construite selon le modèle suivant :
65+```
66+TSomeEnumerator = class
67+ public
68+ function MoveNext: Boolean;
69+ property Current: TSomeType;
70+ end;
71+```
72+
73+Il faut seulement deux choses pour définir une classe Enumerateur: une méthode **MoveNext** qui demande à l'énumérateur d'avancer d'un pas et une propriété **Current** qui peut retourner tout type approprié.
74+
75+Par la suite, vous devez ajouter la méthode magique **GetEnumerator** de la classe conteneur qui retourne un **énumérateur** de l'instance.
76+
77+Par exemple:
78+```
79+type
80+ TEnumerableTree = class;
81+
82+ TTreeEnumerator = class
83+ private
84+ FTree: TEnumerableTree;
85+ FCurrent: TNode;
86+ public
87+ constructor Create(ATree: TEnumerableTree);
88+ function MoveNext: Boolean;
89+ property Current: TNode read FCurrent;
90+ end;
91+
92+ TEnumerableTree = class
93+ public
94+ function GetEnumerator: TTreeEnumerator;
95+ end;
96+
97+ constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
98+ begin
99+ inherited Create;
100+ FTree := ATree;
101+ FCurrent := nil;
102+ end;
103+
104+ function TTreeEnumerator.MoveNext: Boolean;
105+ begin
106+ // pour obtenir le nœud suivant dans l'arbre
107+ if FCurrent = nil then
108+ FCurrent := FTree.GetFirstNode
109+ else
110+ FCurrent := FTree.GetNextNode(FCurrent);
111+ Result := FCurrent <> nil;
112+ end;
113+
114+ function TEnumerableTree.GetEnumerator: TTreeEnumerator;
115+ begin
116+ Result := TTreeEnumerator.Create(Self);
117+ // Important: l'objet créé est automatiquement libéré par le compilateur après la boucle.
118+ end;
119+```
120+
121+Ensuite, cela vous permet de d'exécuter le code suivant:
122+```
123+procedure TreeLoop(ATree: TEnumerableTree);
124+ var
125+ ANode: TNode;
126+ begin
127+ for ANode in ATree do
128+ DoSomething(ANode);
129+ end;
130+```
131+
132+Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés.
133+
134+Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable:
135+```
136+IEnumerable = interface(IInterface)
137+ function GetEnumerator: IEnumerator;
138+ end;
139+```
140+
141+où **IEnumerator** est déclarée comme :
142+```
143+IEnumerator = interface(IInterface)
144+ function GetCurrent: TObject;
145+ function MoveNext: Boolean;
146+ procedure Reset;
147+ property Current: TObject read GetCurrent;
148+ end;
149+```
150+
151+## Enumérateurs multiples pour une classe
152+
153+Vous pouvez ajouter des énumérateurs supplémentaires à des classes, des objets et des enregistrements.
154+
155+Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse:
156+```
157+type
158+ TEnumerableTree = class;
159+
160+ TTreeEnumerator = class
161+ // ...pour la traversée dans l'ordre, voir ci-dessus...
162+ end;
163+
164+ TTreeReverseEnumerator = class
165+ private
166+ FTree: TEnumerableTree;
167+ FCurrent: TNode;
168+ public
169+ constructor Create(ATree: TEnumerableTree);
170+ function MoveNext: Boolean;
171+ property Current: TNode read FCurrent;
172+ function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même
173+ end;
174+
175+ TEnumerableTree = class
176+ public
177+ function GetEnumerator: TTreeEnumerator;
178+ function GetReverseEnumerator: TTreeReverseEnumerator;
179+ end;
180+
181+ //...voir ci-dessus l'implémentation de TTreeEnumerator...
182+
183+ constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
184+ begin
185+ inherited Create;
186+ FTree := ATree;
187+ end;
188+
189+ function TTreeReverseEnumerator.MoveNext: Boolean;
190+ begin
191+ // some logic to get the next node from a tree in reverse order
192+ if FCurrent = nil then
193+ FCurrent := FTree.GetLastNode
194+ else
195+ FCurrent := FTree.GetPrevNode(FCurrent);
196+ Result := FCurrent <> nil;
197+ end;
198+
199+ function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
200+ begin
201+ Result := Self;
202+ end;
203+
204+ function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
205+ begin
206+ Result := TTreeReverseEnumerator.Create(Self);
207+ // Note: l'objet est automatiquement supprimé par le compilateur après la boucle.
208+ end;
209+```
210+
211+Ensuite vous êtes en mesure d'exécuter le code suivant:
212+```
213+procedure TreeLoop(ATree: TEnumerableTree);
214+ var
215+ ANode: TNode;
216+ begin
217+ for ANode in ATree.GetReverseEnumerator do
218+ DoSomething(ANode);
219+ end;
220+```
221+
222+# Extensions FPC
223+
224+Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC ; elles ne sont pas supportées par Delphi.
225+
226+## Traversée de types énumération et intervalle
227+
228+Dans Delphi, il n'est [pas possible de traverser non plus les types énumérés](<http://qc.embarcadero.com/wc/qcmain.aspx?d=106212>) ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit:
229+```
230+type
231+ TColor = (clRed, clBlue, clBlack);
232+ TRange = 'a'..'z';
233+ var
234+ Color: TColor;
235+ ch: Char;
236+ begin
237+ for Color in TColor do
238+ DoSomething(Color);
239+ for ch in TRange do
240+ DoSomethingOther(ch);
241+ end.
242+```
243+
244+## Déclarer des énumérateurs
245+
246+Il n'est pas non plus possible dans Delphi d'ajouter un énumérateur sans modifier la classe, ni d'ajouter un énumérateur d'un type non class object/record/interface. FPC rend cela possible en utilisant la nouvelle syntaxe **operator** type **Enumerator**. Comme dans l'exemple suivant:
247+```
248+type
249+ TMyRecord = record F1: Integer; F2: array of TMyType; end;
250+ TMyArrayEnumerator = class
251+ private
252+ function GetCurrent: TMyType;
253+ public
254+ constructor Create(const A: TMyRecord);
255+ property Current: TMyType read GetCurrent;
256+ function MoveNext: Boolean;
257+ end;
258+
259+ // C'est le nouveau opérateur intégré.
260+ operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
261+ begin
262+ Result := TMyArrayEnumerator.Create(A);
263+ end;
264+
265+ var
266+ A: MyRecord;
267+ V: TMyType
268+ begin
269+ for V in A do
270+ DoSomething(V);
271+ end.
272+```
273+
274+### Traversée de chaînes UTF-8
275+
276+Comme exemple particulièrement utile, l'extension ci-dessous autorise une traversée très efficace des chaînes UTF-8:
277+```
278+uses
279+ LazUTF8;
280+ interface
281+ type
282+ { TUTF8StringAsStringEnumerator
283+ La traversée de 'codepoint' UTF8 est utile quand vous voulez connaître l'encodage exact d'un
284+ caractère UTF-8 ou si vous aimez utiliser des constante de chaîne dans votre code.
285+ Pour des raisons de sécurité, vous pourriez utiliser les valeurs des points de code (cardinaux) à la place.
286+ Si la vitesse importe, n'utilisez pas des énumérateurs. Employez à la place directement le PChar
287+ comme montré dans la méthode MoveNext et lisez sur l'UTF8. Il a des caractéristiques intéressantes. }
288+
289+ TUTF8StringAsStringEnumerator = class
290+ private
291+ fCurrent: UTF8String;
292+ fCurrentPos, fEndPos: PChar;
293+ function GetCurrent: UTF8String;
294+ public
295+ constructor Create(const A: UTF8String);
296+ property Current: UTF8String read GetCurrent;
297+ function MoveNext: Boolean;
298+ end;
299+
300+ operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
301+
302+ var
303+ Form1: TForm1;
304+
305+ implementation
306+
307+ operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
308+ begin
309+ Result := TUTF8StringAsStringEnumerator.Create(A);
310+ end;
311+
312+ { TUTF8StringAsStringEnumerator }
313+
314+ function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
315+ begin
316+ Result:=fCurrent;
317+ end;
318+
319+ constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
320+ begin
321+ fCurrentPos:=PChar(A); // Note: si A='' alors PChar(A) retourne un pointeur sur une chaîne #0
322+ fEndPos:=fCurrentPos+length(A);
323+ end;
324+
325+ function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
326+ var
327+ l: Integer;
328+ begin
329+ if fCurrentPos<fEndPos then
330+ begin
331+ l:=UTF8CharacterLength(fCurrentPos);
332+ SetLength(fCurrent,l);
333+ Move(fCurrentPos^,fCurrent[1],l);
334+ inc(fCurrentPos,l);
335+ Result:=true;
336+ end else
337+ Result:=false;
338+ end;
339+
340+ { TForm1 }
341+
342+ procedure TForm1.FormCreate(Sender: TObject);
343+ var
344+ s, ch: UTF8String;
345+ i: SizeInt;
346+ begin
347+ s:='mäßig';
348+
349+ // Utiliser UTF8Length et UTF8Copy de cette manière est très lent, nécessitant O(n)^2
350+ for i:=1 to UTF8Length(s) do
351+ writeln('ch=',UTF8Copy(s,i,1));
352+
353+ // L'emploi de l'énumérateur du dessus est plus court et plutôt rapide, nécessitant O(n)
354+ for ch in s do
355+ writeln('ch=',ch);
356+ end;
357+```
358+
359+## Utiliser des identificateurs au lieu de MoveNext et Current
360+
361+En Delphi, vous devez utiliser une fonction avec le nom 'MoveNext' et une propriété avec le nom 'Current' dans les énumérateurs. Avec FPC, vous pouvez choisir n'importe quel nom. Cela est permis par l'emploi du modifieur **enumerator** , avec la syntaxe des modifieurs 'enumerator MoveNext;' et 'enumerator Current;'. Comme dans l'exemple suivant :
362+```
363+type
364+ { TMyListEnumerator }
365+
366+ TMyListEnumerator = object
367+ private
368+ FCurrent: Integer;
369+ public
370+ constructor Create;
371+ destructor Destroy;
372+ function StepNext: Boolean; enumerator MoveNext;
373+ property Value: Integer read FCurrent; enumerator Current;
374+ end;
375+
376+ TMyList = class
377+ end;
378+
379+ { TMyListEnumerator }
380+
381+ constructor TMyListEnumerator.Create;
382+ begin
383+ FCurrent := 0;
384+ end;
385+
386+ destructor TMyListEnumerator.Destroy;
387+ begin
388+ inherited;
389+ end;
390+
391+ function TMyListEnumerator.StepNext: Boolean;
392+ begin
393+ inc(FCurrent);
394+ Result := FCurrent <= 3;
395+ end;
396+
397+ operator enumerator (AList: TMyList): TMyListEnumerator;
398+ begin
399+ Result.Create;
400+ end;
401+
402+ var
403+ List: TMyList;
404+ i: integer;
405+ begin
406+ List := TMyList.Create;
407+ for i in List do
408+ WriteLn(i);
409+ List.Free;
410+ end.
411+```
412+
413+# Extensions proposées
414+
415+## Donner la position de l'énumérateur si disponible
416+
417+### Problématique
418+
419+Il est impossible d'extraire toute information de l'itérateur sauf l'article courant. Parfois d'autres informations, comme l'index courant, pourraient être utiles :
420+```
421+type
422+ TUTF8StringEnumerator = class
423+ private
424+ FByteIndex: Integer;
425+ FCharIndex: Integer;
426+ public
427+ constructor Create(const A: UTF8String);
428+ function Current: UTF8Char;
429+ function CurrentIndex: Integer;
430+ function MoveNext: Boolean;
431+ end;
432+
433+ operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
434+ begin
435+ Result := TUTF8String.Create(A);
436+ end;
437+
438+ var
439+ s: UTF8String;
440+ ch: UTF8Char;
441+ i: Integer;
442+ begin
443+
444+ // inefficace comme discuté plus haut
445+ for i := 1 to Length(s) do
446+ Writeln(i, ': ', ch[i]);
447+
448+ // Ok, mais moche
449+ i := 1;
450+ for ch in s do begin
451+ Writeln(i, ': ', ch);
452+ Inc(i);
453+ end;
454+
455+ // Extension proposée
456+ for ch in s index i do
457+ Writeln(i, ': ', ch);
458+
459+ // Extension proposée pour traverser à l'envers (équivalent au downto)
460+ for ch in reverse s do
461+ Writeln(i, ': ', ch);
462+
463+ // Avec l'extension d'index proposée
464+ for ch in reverse s index i do
465+ Writeln(i, ': ', ch);
466+ end.
467+```
468+
469+Remarquez que l'index doit être conçu pour retourner un type arbitraire (c'est-à-dire pas forcément un entier). Par exemple, dans l'exemple d'une traversée d'arbre, l'index pourrait retourner un tableau de nœuds décrivant le chemin allant de la racine au nœud courant.
470+
471+### Solution de contournement pour l'indice
472+
473+La propriété Current prend la forme d'un couple (Indice,Valeur), on emploie un type générique afin de pérenniser cette conception.
474+```
475+unit IndicedEnumerators;
476+
477+ interface
478+
479+ type
480+
481+ generic TGIndicedEnumerator<TContainer, TElement> = class
482+ public type
483+ TIndicedValue = record
484+ Value: TElement;
485+ Ind: Integer;
486+ end;
487+ protected
488+ FCntnr: TContainer;
489+ FCurrent: TIndicedValue;
490+ public
491+ constructor Create(aCntnr: TContainer); virtual;
492+ function MoveNext: Boolean; virtual; abstract;
493+ property Current: TIndicedValue read FCurrent;
494+ function GetEnumerator: TGIndicedEnumerator;
495+ end;
496+
497+ TCsvAnsiStringFieldEnumerator = class(specialize TGIndicedEnumerator<AnsiString, AnsiString>)
498+ private
499+ FCurrPos: Integer;
500+ FSeparator: Char;
501+ public
502+ constructor Create(aString: AnsiString; Separator: AnsiChar); overload;
503+ function MoveNext: Boolean; override;
504+ end;
505+
506+ implementation
507+
508+ { TGIndicedEnumerator }
509+
510+ constructor TGIndicedEnumerator.Create(aCntnr: TContainer);
511+ begin
512+ FCntnr := aCntnr;
513+ end;
514+
515+ function TGIndicedEnumerator.GetEnumerator: TGIndicedEnumerator;
516+ begin
517+ Result := Self;
518+ end;
519+
520+ { TCsvAnsiStringFieldEnumerator }
521+
522+ constructor TCsvAnsiStringFieldEnumerator.Create(aString: AnsiString; Separator: AnsiChar);
523+ begin
524+ inherited Create(aString);
525+ FCurrPos := 0;
526+ FSeparator := Separator;
527+ FCurrent.Ind := -1;
528+ end;
529+
530+ function TCsvAnsiStringFieldEnumerator.MoveNext: Boolean;
531+ begin
532+ if FCurrPos > Length(FCntnr) then exit(False);
533+ // Premier élément
534+ Inc(FCurrent.ind);
535+ FCurrent.Value := '';
536+ Inc(FCurrPos);
537+ while (FCurrPos <= Length(FCntnr)) and (FCntnr[FCurrPos] <> FSeparator) do
538+ begin
539+ FCurrent.Value += FCntnr[FCurrPos];
540+ Inc(FCurrPos);
541+ end;
542+ Result := True;
543+ end;
544+
545+ end.
546+```
547+
548+Un point intéressant se trouve dans la déclaration de GetEnumerator dont le type de retour est le type énumérateur lui-même, nécessaire pour pouvoir créer un itérateur à partir de la classe elle-même, mais surtout la méthode sera correctement adaptée lors de la spécialisation et retournera alors le type spécialisé (TCsvAnsiStringFieldEnumerator dans notre exemple). Ce qui montre que la substitution lors de spécialisation ne se limite aux seules marques substitutives ("placeholders").
549+
550+Dans l'exemple, la spécialisation et la dérivation sont réalisées simultanément mais ce n'est pas une nécessité, il est d'ailleurs sans doute préférable de scinder ces deux opérations et de créer un type intermédiaire spécialisé TAnsiStringEnumerator qui sans introduire la notion de séparateur permettrait de concevoir d'autres itérateurs sur des AnsiString.
551+
552+L'enregistrement couple (Indice,Valeur) lui-même générique sera automatiquement adapté lors de la spécialisation, cependant, son nom de type devra être qualifié à l'aide du nom de la classe d'énumérateur spécialisé comme nous pouvons le voir dans le petit programme d'exemple, afin de lever toute ambiguïté si deux types spécialisés devaient cohabiter :
553+```
554+#!/usr/bin/instantfpc
555+
556+ // (see the Instantfpc doc)
557+
558+ {$mode objfpc}{$H+}
559+
560+ uses
561+ SysUtils,
562+ IndicedEnumerators;
563+
564+ var
565+ IndStr: TCsvAnsiStringFieldEnumerator.TIndicedValue;
566+
567+ begin
568+ // énumérateur obtenu directement à partir de la classe elle-même, la libération de l'énumérateur
569+ // est garantie en sortie de la boucle for .. in
570+ for IndStr in TCsvAnsiStringFieldEnumerator.Create(ParamStr(1), ';') do
571+ WriteLn(Format('%d : "%s"', [IndStr.Ind, IndStr.Value]));
572+ end.
573+```
574+
575+Appel du script InstantFPC en ligne de commande (nom du fichier source pascal avec des droit d'exécution sous Linux + un paramètre)
576+```
577+./TestStringFieldEnums.pas "small;string;with;separated;fields"
578+```
579+
580+Cette solution a l'avantage de mettre à disposition un indice même si celui-ci n'existe pas nativement dans le type traversé, sans avoir à déclarer une variable Indice dans le code client.
581+
582+Enfin, l'énumérateur générique pourrait être amélioré en ajoutant aux paramètres du constructeur un indice de départ (0 devenant la valeur par défaut).
583+
584+# Référence
585+
586+ * <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
587+
588+---
589+*From the Free Pascal wiki, page "for-in loop/fr", as the Internet Archive kept it on 2024-09-08. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history