Sign in Join
Wiki copytemporary

for-in loop/fr: what changed

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

OldNewLine
11La 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.
22
33## Contents
44
55 * 1 Implémentation Delphi et FPC
66 * 1.1 Boucle sur une chaîne
77 * 1.2 Boucle sur un tableau
88 * 1.3 Boucle sur un ensemble
99 * 1.4 Traverser un conteneur
1010 * 1.5 Enumérateurs multiples pour une classe
1111 * 2 Extensions FPC
1212 * 2.1 Traversée de types énumération et intervalle
1313 * 2.2 Déclarer des énumérateurs
1414 * 2.2.1 Traversée de chaînes UTF-8
1515 * 2.3 Utiliser des identificateurs au lieu de MoveNext et Current
1616 * 3 Extensions proposées
1717 * 3.1 Donner la position de l'énumérateur si disponible
1818 * 3.1.1 Problématique
1919 * 3.1.2 Solution de contournement pour l'indice
2020 * 4 Référence
2121
2222# Implémentation Delphi et FPC
2323
2424Une boucle **for in** respecte la syntaxe suivante:
2525
2626## Boucle sur une chaîne
2727```
2828procedure StringLoop(S: String);
2929 var
3030 C: Char;
3131 begin
3232 for C in S do
3333 DoSomething(C);
3434 end;
3535```
3636
3737## Boucle sur un tableau
3838```
3939procedure ArrayLoop(A: Array of Byte);
4040 var
4141 B: Byte;
4242 begin
4343 for B in A do
4444 DoSomething(B);
4545 end;
4646```
4747
4848## Boucle sur un ensemble
4949```
5050type
5151 TColor = (cRed, cGren, cBlue);
5252 TColors = set of TColor;
5353 procedure SetLoop(Colors: TColors);
5454 var
5555 Color: TColor;
5656 begin
5757 for Color in Colors do
5858 DoSomething(Color);
5959 end;
6060```
6161
6262## Traverser un conteneur
6363
6464Pour travrser une classe conteneur, vous avez besoin d'ajouter un **énumérateur**. Un **Enumérateur** est une classe construite selon le modèle suivant :
6565```
6666TSomeEnumerator = class
6767 public
6868 function MoveNext: Boolean;
6969 property Current: TSomeType;
7070 end;
7171```
7272
7373Il 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é.
7474
7575Par la suite, vous devez ajouter la méthode magique **GetEnumerator** de la classe conteneur qui retourne un **énumérateur** de l'instance.
7676
7777Par exemple:
7878```
7979type
8080 TEnumerableTree = class;
8181
8282 TTreeEnumerator = class
8383 private
8484 FTree: TEnumerableTree;
8585 FCurrent: TNode;
8686 public
8787 constructor Create(ATree: TEnumerableTree);
8888 function MoveNext: Boolean;
8989 property Current: TNode read FCurrent;
9090 end;
9191
9292 TEnumerableTree = class
9393 public
9494 function GetEnumerator: TTreeEnumerator;
9595 end;
9696
9797 constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
9898 begin
9999 inherited Create;
100100 FTree := ATree;
101101 FCurrent := nil;
102102 end;
103103
104104 function TTreeEnumerator.MoveNext: Boolean;
105105 begin
106106 // pour obtenir le nœud suivant dans l'arbre
107107 if FCurrent = nil then
108108 FCurrent := FTree.GetFirstNode
109109 else
110110 FCurrent := FTree.GetNextNode(FCurrent);
111111 Result := FCurrent <> nil;
112112 end;
113113
114114 function TEnumerableTree.GetEnumerator: TTreeEnumerator;
115115 begin
116116 Result := TTreeEnumerator.Create(Self);
117117 // Important: l'objet créé est automatiquement libéré par le compilateur après la boucle.
118118 end;
119119```
120120
121121Ensuite, cela vous permet de d'exécuter le code suivant:
122122```
123123procedure TreeLoop(ATree: TEnumerableTree);
124124 var
125125 ANode: TNode;
126126 begin
127127 for ANode in ATree do
128128 DoSomething(ANode);
129129 end;
130130```
131131
132132Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés.
133133
134134Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable:
135135```
136136IEnumerable = interface(IInterface)
137137 function GetEnumerator: IEnumerator;
138138 end;
139139```
140140
141141où **IEnumerator** est déclarée comme :
142142```
143143IEnumerator = interface(IInterface)
144144 function GetCurrent: TObject;
145145 function MoveNext: Boolean;
146146 procedure Reset;
147147 property Current: TObject read GetCurrent;
148148 end;
149149```
150150
151151## Enumérateurs multiples pour une classe
152152
153153Vous pouvez ajouter des énumérateurs supplémentaires à des classes, des objets et des enregistrements.
154154
155155Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse:
156156```
157157type
158158 TEnumerableTree = class;
159159
160160 TTreeEnumerator = class
161161 // ...pour la traversée dans l'ordre, voir ci-dessus...
162162 end;
163163
164164 TTreeReverseEnumerator = class
165165 private
166166 FTree: TEnumerableTree;
167167 FCurrent: TNode;
168168 public
169169 constructor Create(ATree: TEnumerableTree);
170170 function MoveNext: Boolean;
171171 property Current: TNode read FCurrent;
172172 function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même
173173 end;
174174
175175 TEnumerableTree = class
176176 public
177177 function GetEnumerator: TTreeEnumerator;
178178 function GetReverseEnumerator: TTreeReverseEnumerator;
179179 end;
180180
181181 //...voir ci-dessus l'implémentation de TTreeEnumerator...
182182
183183 constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
184184 begin
185185 inherited Create;
186186 FTree := ATree;
187187 end;
188188
189189 function TTreeReverseEnumerator.MoveNext: Boolean;
190190 begin
191191 // some logic to get the next node from a tree in reverse order
192192 if FCurrent = nil then
193193 FCurrent := FTree.GetLastNode
194194 else
195195 FCurrent := FTree.GetPrevNode(FCurrent);
196196 Result := FCurrent <> nil;
197197 end;
198198
199199 function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
200200 begin
201201 Result := Self;
202202 end;
203203
204204 function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
205205 begin
206206 Result := TTreeReverseEnumerator.Create(Self);
207207 // Note: l'objet est automatiquement supprimé par le compilateur après la boucle.
208208 end;
209209```
210210
211211Ensuite vous êtes en mesure d'exécuter le code suivant:
212212```
213213procedure TreeLoop(ATree: TEnumerableTree);
214214 var
215215 ANode: TNode;
216216 begin
217217 for ANode in ATree.GetReverseEnumerator do
218218 DoSomething(ANode);
219219 end;
220220```
221221
222222# Extensions FPC
223223
224224Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC ; elles ne sont pas supportées par Delphi.
225225
226226## Traversée de types énumération et intervalle
227227
228228Dans 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:
229229```
230230type
231231 TColor = (clRed, clBlue, clBlack);
232232 TRange = 'a'..'z';
233233 var
234234 Color: TColor;
235235 ch: Char;
236236 begin
237237 for Color in TColor do
238238 DoSomething(Color);
239239 for ch in TRange do
240240 DoSomethingOther(ch);
241241 end.
242242```
243243
244244## Déclarer des énumérateurs
245245
246246Il 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:
247247```
248248type
249249 TMyRecord = record F1: Integer; F2: array of TMyType; end;
250250 TMyArrayEnumerator = class
251251 private
252252 function GetCurrent: TMyType;
253253 public
254254 constructor Create(const A: TMyRecord);
255255 property Current: TMyType read GetCurrent;
256256 function MoveNext: Boolean;
257257 end;
258258
259259 // C'est le nouveau opérateur intégré.
260260 operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
261261 begin
262262 Result := TMyArrayEnumerator.Create(A);
263263 end;
264264
265265 var
266266 A: MyRecord;
267267 V: TMyType
268268 begin
269269 for V in A do
270270 DoSomething(V);
271271 end.
272272```
273273
274274### Traversée de chaînes UTF-8
275275
276276Comme exemple particulièrement utile, l'extension ci-dessous autorise une traversée très efficace des chaînes UTF-8:
277277```
278278uses
279279 LazUTF8;
280280 interface
281281 type
282282 { TUTF8StringAsStringEnumerator
283283 La traversée de 'codepoint' UTF8 est utile quand vous voulez connaître l'encodage exact d'un
284284 caractère UTF-8 ou si vous aimez utiliser des constante de chaîne dans votre code.
285285 Pour des raisons de sécurité, vous pourriez utiliser les valeurs des points de code (cardinaux) à la place.
286286 Si la vitesse importe, n'utilisez pas des énumérateurs. Employez à la place directement le PChar
287287 comme montré dans la méthode MoveNext et lisez sur l'UTF8. Il a des caractéristiques intéressantes. }
288288
289289 TUTF8StringAsStringEnumerator = class
290290 private
291291 fCurrent: UTF8String;
292292 fCurrentPos, fEndPos: PChar;
293293 function GetCurrent: UTF8String;
294294 public
295295 constructor Create(const A: UTF8String);
296296 property Current: UTF8String read GetCurrent;
297297 function MoveNext: Boolean;
298298 end;
299299
300300 operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
301301
302302 var
303303 Form1: TForm1;
304304
305305 implementation
306306
307307 operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
308308 begin
309309 Result := TUTF8StringAsStringEnumerator.Create(A);
310310 end;
311311
312312 { TUTF8StringAsStringEnumerator }
313313
314314 function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
315315 begin
316316 Result:=fCurrent;
317317 end;
318318
319319 constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
320320 begin
321321 fCurrentPos:=PChar(A); // Note: si A='' alors PChar(A) retourne un pointeur sur une chaîne #0
322322 fEndPos:=fCurrentPos+length(A);
323323 end;
324324
325325 function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
326326 var
327327 l: Integer;
328328 begin
329329 if fCurrentPos<fEndPos then
330330 begin
331331 l:=UTF8CharacterLength(fCurrentPos);
332332 SetLength(fCurrent,l);
333333 Move(fCurrentPos^,fCurrent[1],l);
334334 inc(fCurrentPos,l);
335335 Result:=true;
336336 end else
337337 Result:=false;
338338 end;
339339
340340 { TForm1 }
341341
342342 procedure TForm1.FormCreate(Sender: TObject);
343343 var
344344 s, ch: UTF8String;
345345 i: SizeInt;
346346 begin
347347 s:='mäßig';
348348
349349 // Utiliser UTF8Length et UTF8Copy de cette manière est très lent, nécessitant O(n)^2
350350 for i:=1 to UTF8Length(s) do
351351 writeln('ch=',UTF8Copy(s,i,1));
352352
353353 // L'emploi de l'énumérateur du dessus est plus court et plutôt rapide, nécessitant O(n)
354354 for ch in s do
355355 writeln('ch=',ch);
356356 end;
357357```
358358
359359## Utiliser des identificateurs au lieu de MoveNext et Current
360360
361361En 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 :
362362```
363363type
364364 { TMyListEnumerator }
365365
366366 TMyListEnumerator = object
367367 private
368368 FCurrent: Integer;
369369 public
370370 constructor Create;
371371 destructor Destroy;
372372 function StepNext: Boolean; enumerator MoveNext;
373373 property Value: Integer read FCurrent; enumerator Current;
374374 end;
375375
376376 TMyList = class
377377 end;
378378
379379 { TMyListEnumerator }
380380
381381 constructor TMyListEnumerator.Create;
382382 begin
383383 FCurrent := 0;
384384 end;
385385
386386 destructor TMyListEnumerator.Destroy;
387387 begin
388388 inherited;
389389 end;
390390
391391 function TMyListEnumerator.StepNext: Boolean;
392392 begin
393393 inc(FCurrent);
394394 Result := FCurrent <= 3;
395395 end;
396396
397397 operator enumerator (AList: TMyList): TMyListEnumerator;
398398 begin
399399 Result.Create;
400400 end;
401401
402402 var
403403 List: TMyList;
404404 i: integer;
405405 begin
406406 List := TMyList.Create;
407407 for i in List do
408408 WriteLn(i);
409409 List.Free;
410410 end.
411411```
412412
413413# Extensions proposées
414414
415415## Donner la position de l'énumérateur si disponible
416416
417417### Problématique
418418
419419Il 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 :
420420```
421421type
422422 TUTF8StringEnumerator = class
423423 private
424424 FByteIndex: Integer;
425425 FCharIndex: Integer;
426426 public
427427 constructor Create(const A: UTF8String);
428428 function Current: UTF8Char;
429429 function CurrentIndex: Integer;
430430 function MoveNext: Boolean;
431431 end;
432432
433433 operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
434434 begin
435435 Result := TUTF8String.Create(A);
436436 end;
437437
438438 var
439439 s: UTF8String;
440440 ch: UTF8Char;
441441 i: Integer;
442442 begin
443443
444444 // inefficace comme discuté plus haut
445445 for i := 1 to Length(s) do
446446 Writeln(i, ': ', ch[i]);
447447
448448 // Ok, mais moche
449449 i := 1;
450450 for ch in s do begin
451451 Writeln(i, ': ', ch);
452452 Inc(i);
453453 end;
454454
455455 // Extension proposée
456456 for ch in s index i do
457457 Writeln(i, ': ', ch);
458458
459459 // Extension proposée pour traverser à l'envers (équivalent au downto)
460460 for ch in reverse s do
461461 Writeln(i, ': ', ch);
462462
463463 // Avec l'extension d'index proposée
464464 for ch in reverse s index i do
465465 Writeln(i, ': ', ch);
466466 end.
467467```
468468
469469Remarquez 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.
470470
471471### Solution de contournement pour l'indice
472472
473473La propriété Current prend la forme d'un couple (Indice,Valeur), on emploie un type générique afin de pérenniser cette conception.
474474```
475475unit IndicedEnumerators;
476476
477477 interface
478478
479479 type
480480
481481 generic TGIndicedEnumerator<TContainer, TElement> = class
482482 public type
483483 TIndicedValue = record
484484 Value: TElement;
485485 Ind: Integer;
486486 end;
487487 protected
488488 FCntnr: TContainer;
489489 FCurrent: TIndicedValue;
490490 public
491491 constructor Create(aCntnr: TContainer); virtual;
492492 function MoveNext: Boolean; virtual; abstract;
493493 property Current: TIndicedValue read FCurrent;
494494 function GetEnumerator: TGIndicedEnumerator;
495495 end;
496496
497497 TCsvAnsiStringFieldEnumerator = class(specialize TGIndicedEnumerator<AnsiString, AnsiString>)
498498 private
499499 FCurrPos: Integer;
500500 FSeparator: Char;
501501 public
502502 constructor Create(aString: AnsiString; Separator: AnsiChar); overload;
503503 function MoveNext: Boolean; override;
504504 end;
505505
506506 implementation
507507
508508 { TGIndicedEnumerator }
509509
510510 constructor TGIndicedEnumerator.Create(aCntnr: TContainer);
511511 begin
512512 FCntnr := aCntnr;
513513 end;
514514
515515 function TGIndicedEnumerator.GetEnumerator: TGIndicedEnumerator;
516516 begin
517517 Result := Self;
518518 end;
519519
520520 { TCsvAnsiStringFieldEnumerator }
521521
522522 constructor TCsvAnsiStringFieldEnumerator.Create(aString: AnsiString; Separator: AnsiChar);
523523 begin
524524 inherited Create(aString);
525525 FCurrPos := 0;
526526 FSeparator := Separator;
527527 FCurrent.Ind := -1;
528528 end;
529529
530530 function TCsvAnsiStringFieldEnumerator.MoveNext: Boolean;
531531 begin
532532 if FCurrPos > Length(FCntnr) then exit(False);
533533 // Premier élément
534534 Inc(FCurrent.ind);
535535 FCurrent.Value := '';
536536 Inc(FCurrPos);
537537 while (FCurrPos <= Length(FCntnr)) and (FCntnr[FCurrPos] <> FSeparator) do
538538 begin
539539 FCurrent.Value += FCntnr[FCurrPos];
540540 Inc(FCurrPos);
541541 end;
542542 Result := True;
543543 end;
544544
545545 end.
546546```
547547
548548Un 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").
549549
550550Dans 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.
551551
552552L'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 :
553553```
554554#!/usr/bin/instantfpc
555555
556556 // (see the Instantfpc doc)
557557
558558 {$mode objfpc}{$H+}
559559
560560 uses
561561 SysUtils,
562562 IndicedEnumerators;
563563
564564 var
565565 IndStr: TCsvAnsiStringFieldEnumerator.TIndicedValue;
566566
567567 begin
568568 // énumérateur obtenu directement à partir de la classe elle-même, la libération de l'énumérateur
569569 // est garantie en sortie de la boucle for .. in
570570 for IndStr in TCsvAnsiStringFieldEnumerator.Create(ParamStr(1), ';') do
571571 WriteLn(Format('%d : "%s"', [IndStr.Ind, IndStr.Value]));
572572 end.
573573```
574574
575575Appel du script InstantFPC en ligne de commande (nom du fichier source pascal avec des droit d'exécution sous Linux + un paramètre)
576576```
577577./TestStringFieldEnums.pas "small;string;with;separated;fields"
578578```
579579
580580Cette 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.
581581
582582Enfin, 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).
583583
584584# Référence
585585
586586 * <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
587587
588588---
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).*
589+*From the Free Pascal wiki, page "for-in loop/fr", as the Internet Archive kept it on 2024-09-08, last changed on 2020-02-16. 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