Sign in Join
Wiki copytemporary

for-in loop/fr: what changed

Revision 2 to 3. 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
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-
223# Implémentation Delphi et FPC
234
245Une boucle **for in** respecte la syntaxe suivante:
256
267## Boucle sur une chaîne
278```
289procedure StringLoop(S: String);
2910 var
3011 C: Char;
3112 begin
3213 for C in S do
3314 DoSomething(C);
3415 end;
3516```
3617
3718## Boucle sur un tableau
3819```
3920procedure ArrayLoop(A: Array of Byte);
4021 var
4122 B: Byte;
4223 begin
4324 for B in A do
4425 DoSomething(B);
4526 end;
4627```
4728
4829## Boucle sur un ensemble
4930```
5031type
5132 TColor = (cRed, cGren, cBlue);
5233 TColors = set of TColor;
5334 procedure SetLoop(Colors: TColors);
5435 var
5536 Color: TColor;
5637 begin
5738 for Color in Colors do
5839 DoSomething(Color);
5940 end;
6041```
6142
6243## Traverser un conteneur
6344
6445Pour travrser une classe conteneur, vous avez besoin d'ajouter un **énumérateur**. Un **Enumérateur** est une classe construite selon le modèle suivant :
6546```
6647TSomeEnumerator = class
6748 public
6849 function MoveNext: Boolean;
6950 property Current: TSomeType;
7051 end;
7152```
7253
7354Il 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é.
7455
7556Par la suite, vous devez ajouter la méthode magique **GetEnumerator** de la classe conteneur qui retourne un **énumérateur** de l'instance.
7657
7758Par exemple:
7859```
7960type
8061 TEnumerableTree = class;
8162
8263 TTreeEnumerator = class
8364 private
8465 FTree: TEnumerableTree;
8566 FCurrent: TNode;
8667 public
8768 constructor Create(ATree: TEnumerableTree);
8869 function MoveNext: Boolean;
8970 property Current: TNode read FCurrent;
9071 end;
9172
9273 TEnumerableTree = class
9374 public
9475 function GetEnumerator: TTreeEnumerator;
9576 end;
9677
9778 constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
9879 begin
9980 inherited Create;
10081 FTree := ATree;
10182 FCurrent := nil;
10283 end;
10384
10485 function TTreeEnumerator.MoveNext: Boolean;
10586 begin
10687 // pour obtenir le nœud suivant dans l'arbre
10788 if FCurrent = nil then
10889 FCurrent := FTree.GetFirstNode
10990 else
11091 FCurrent := FTree.GetNextNode(FCurrent);
11192 Result := FCurrent <> nil;
11293 end;
11394
11495 function TEnumerableTree.GetEnumerator: TTreeEnumerator;
11596 begin
11697 Result := TTreeEnumerator.Create(Self);
11798 // Important: l'objet créé est automatiquement libéré par le compilateur après la boucle.
11899 end;
119100```
120101
121102Ensuite, cela vous permet de d'exécuter le code suivant:
122103```
123104procedure TreeLoop(ATree: TEnumerableTree);
124105 var
125106 ANode: TNode;
126107 begin
127108 for ANode in ATree do
128109 DoSomething(ANode);
129110 end;
130111```
131112
132113Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés.
133114
134115Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable:
135116```
136117IEnumerable = interface(IInterface)
137118 function GetEnumerator: IEnumerator;
138119 end;
139120```
140121
141122où **IEnumerator** est déclarée comme :
142123```
143124IEnumerator = interface(IInterface)
144125 function GetCurrent: TObject;
145126 function MoveNext: Boolean;
146127 procedure Reset;
147128 property Current: TObject read GetCurrent;
148129 end;
149130```
150131
151132## Enumérateurs multiples pour une classe
152133
153134Vous pouvez ajouter des énumérateurs supplémentaires à des classes, des objets et des enregistrements.
154135
155136Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse:
156137```
157138type
158139 TEnumerableTree = class;
159140
160141 TTreeEnumerator = class
161142 // ...pour la traversée dans l'ordre, voir ci-dessus...
162143 end;
163144
164145 TTreeReverseEnumerator = class
165146 private
166147 FTree: TEnumerableTree;
167148 FCurrent: TNode;
168149 public
169150 constructor Create(ATree: TEnumerableTree);
170151 function MoveNext: Boolean;
171152 property Current: TNode read FCurrent;
172153 function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même
173154 end;
174155
175156 TEnumerableTree = class
176157 public
177158 function GetEnumerator: TTreeEnumerator;
178159 function GetReverseEnumerator: TTreeReverseEnumerator;
179160 end;
180161
181162 //...voir ci-dessus l'implémentation de TTreeEnumerator...
182163
183164 constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
184165 begin
185166 inherited Create;
186167 FTree := ATree;
187168 end;
188169
189170 function TTreeReverseEnumerator.MoveNext: Boolean;
190171 begin
191172 // some logic to get the next node from a tree in reverse order
192173 if FCurrent = nil then
193174 FCurrent := FTree.GetLastNode
194175 else
195176 FCurrent := FTree.GetPrevNode(FCurrent);
196177 Result := FCurrent <> nil;
197178 end;
198179
199180 function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
200181 begin
201182 Result := Self;
202183 end;
203184
204185 function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
205186 begin
206187 Result := TTreeReverseEnumerator.Create(Self);
207188 // Note: l'objet est automatiquement supprimé par le compilateur après la boucle.
208189 end;
209190```
210191
211192Ensuite vous êtes en mesure d'exécuter le code suivant:
212193```
213194procedure TreeLoop(ATree: TEnumerableTree);
214195 var
215196 ANode: TNode;
216197 begin
217198 for ANode in ATree.GetReverseEnumerator do
218199 DoSomething(ANode);
219200 end;
220201```
221202
222203# Extensions FPC
223204
224205Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC ; elles ne sont pas supportées par Delphi.
225206
226207## Traversée de types énumération et intervalle
227208
228209Dans 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:
229210```
230211type
231212 TColor = (clRed, clBlue, clBlack);
232213 TRange = 'a'..'z';
233214 var
234215 Color: TColor;
235216 ch: Char;
236217 begin
237218 for Color in TColor do
238219 DoSomething(Color);
239220 for ch in TRange do
240221 DoSomethingOther(ch);
241222 end.
242223```
243224
244225## Déclarer des énumérateurs
245226
246227Il 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:
247228```
248229type
249230 TMyRecord = record F1: Integer; F2: array of TMyType; end;
250231 TMyArrayEnumerator = class
251232 private
252233 function GetCurrent: TMyType;
253234 public
254235 constructor Create(const A: TMyRecord);
255236 property Current: TMyType read GetCurrent;
256237 function MoveNext: Boolean;
257238 end;
258239
259240 // C'est le nouveau opérateur intégré.
260241 operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
261242 begin
262243 Result := TMyArrayEnumerator.Create(A);
263244 end;
264245
265246 var
266247 A: MyRecord;
267248 V: TMyType
268249 begin
269250 for V in A do
270251 DoSomething(V);
271252 end.
272253```
273254
274255### Traversée de chaînes UTF-8
275256
276257Comme exemple particulièrement utile, l'extension ci-dessous autorise une traversée très efficace des chaînes UTF-8:
277258```
278259uses
279260 LazUTF8;
280261 interface
281262 type
282263 { TUTF8StringAsStringEnumerator
283264 La traversée de 'codepoint' UTF8 est utile quand vous voulez connaître l'encodage exact d'un
284265 caractère UTF-8 ou si vous aimez utiliser des constante de chaîne dans votre code.
285266 Pour des raisons de sécurité, vous pourriez utiliser les valeurs des points de code (cardinaux) à la place.
286267 Si la vitesse importe, n'utilisez pas des énumérateurs. Employez à la place directement le PChar
287268 comme montré dans la méthode MoveNext et lisez sur l'UTF8. Il a des caractéristiques intéressantes. }
288269
289270 TUTF8StringAsStringEnumerator = class
290271 private
291272 fCurrent: UTF8String;
292273 fCurrentPos, fEndPos: PChar;
293274 function GetCurrent: UTF8String;
294275 public
295276 constructor Create(const A: UTF8String);
296277 property Current: UTF8String read GetCurrent;
297278 function MoveNext: Boolean;
298279 end;
299280
300281 operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
301282
302283 var
303284 Form1: TForm1;
304285
305286 implementation
306287
307288 operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
308289 begin
309290 Result := TUTF8StringAsStringEnumerator.Create(A);
310291 end;
311292
312293 { TUTF8StringAsStringEnumerator }
313294
314295 function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
315296 begin
316297 Result:=fCurrent;
317298 end;
318299
319300 constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
320301 begin
321302 fCurrentPos:=PChar(A); // Note: si A='' alors PChar(A) retourne un pointeur sur une chaîne #0
322303 fEndPos:=fCurrentPos+length(A);
323304 end;
324305
325306 function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
326307 var
327308 l: Integer;
328309 begin
329310 if fCurrentPos<fEndPos then
330311 begin
331312 l:=UTF8CharacterLength(fCurrentPos);
332313 SetLength(fCurrent,l);
333314 Move(fCurrentPos^,fCurrent[1],l);
334315 inc(fCurrentPos,l);
335316 Result:=true;
336317 end else
337318 Result:=false;
338319 end;
339320
340321 { TForm1 }
341322
342323 procedure TForm1.FormCreate(Sender: TObject);
343324 var
344325 s, ch: UTF8String;
345326 i: SizeInt;
346327 begin
347328 s:='mäßig';
348329
349330 // Utiliser UTF8Length et UTF8Copy de cette manière est très lent, nécessitant O(n)^2
350331 for i:=1 to UTF8Length(s) do
351332 writeln('ch=',UTF8Copy(s,i,1));
352333
353334 // L'emploi de l'énumérateur du dessus est plus court et plutôt rapide, nécessitant O(n)
354335 for ch in s do
355336 writeln('ch=',ch);
356337 end;
357338```
358339
359340## Utiliser des identificateurs au lieu de MoveNext et Current
360341
361342En 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 :
362343```
363344type
364345 { TMyListEnumerator }
365346
366347 TMyListEnumerator = object
367348 private
368349 FCurrent: Integer;
369350 public
370351 constructor Create;
371352 destructor Destroy;
372353 function StepNext: Boolean; enumerator MoveNext;
373354 property Value: Integer read FCurrent; enumerator Current;
374355 end;
375356
376357 TMyList = class
377358 end;
378359
379360 { TMyListEnumerator }
380361
381362 constructor TMyListEnumerator.Create;
382363 begin
383364 FCurrent := 0;
384365 end;
385366
386367 destructor TMyListEnumerator.Destroy;
387368 begin
388369 inherited;
389370 end;
390371
391372 function TMyListEnumerator.StepNext: Boolean;
392373 begin
393374 inc(FCurrent);
394375 Result := FCurrent <= 3;
395376 end;
396377
397378 operator enumerator (AList: TMyList): TMyListEnumerator;
398379 begin
399380 Result.Create;
400381 end;
401382
402383 var
403384 List: TMyList;
404385 i: integer;
405386 begin
406387 List := TMyList.Create;
407388 for i in List do
408389 WriteLn(i);
409390 List.Free;
410391 end.
411392```
412393
413394# Extensions proposées
414395
415396## Donner la position de l'énumérateur si disponible
416397
417398### Problématique
418399
419400Il 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 :
420401```
421402type
422403 TUTF8StringEnumerator = class
423404 private
424405 FByteIndex: Integer;
425406 FCharIndex: Integer;
426407 public
427408 constructor Create(const A: UTF8String);
428409 function Current: UTF8Char;
429410 function CurrentIndex: Integer;
430411 function MoveNext: Boolean;
431412 end;
432413
433414 operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
434415 begin
435416 Result := TUTF8String.Create(A);
436417 end;
437418
438419 var
439420 s: UTF8String;
440421 ch: UTF8Char;
441422 i: Integer;
442423 begin
443424
444425 // inefficace comme discuté plus haut
445426 for i := 1 to Length(s) do
446427 Writeln(i, ': ', ch[i]);
447428
448429 // Ok, mais moche
449430 i := 1;
450431 for ch in s do begin
451432 Writeln(i, ': ', ch);
452433 Inc(i);
453434 end;
454435
455436 // Extension proposée
456437 for ch in s index i do
457438 Writeln(i, ': ', ch);
458439
459440 // Extension proposée pour traverser à l'envers (équivalent au downto)
460441 for ch in reverse s do
461442 Writeln(i, ': ', ch);
462443
463444 // Avec l'extension d'index proposée
464445 for ch in reverse s index i do
465446 Writeln(i, ': ', ch);
466447 end.
467448```
468449
469450Remarquez 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.
470451
471452### Solution de contournement pour l'indice
472453
473454La propriété Current prend la forme d'un couple (Indice,Valeur), on emploie un type générique afin de pérenniser cette conception.
474455```
475456unit IndicedEnumerators;
476457
477458 interface
478459
479460 type
480461
481462 generic TGIndicedEnumerator<TContainer, TElement> = class
482463 public type
483464 TIndicedValue = record
484465 Value: TElement;
485466 Ind: Integer;
486467 end;
487468 protected
488469 FCntnr: TContainer;
489470 FCurrent: TIndicedValue;
490471 public
491472 constructor Create(aCntnr: TContainer); virtual;
492473 function MoveNext: Boolean; virtual; abstract;
493474 property Current: TIndicedValue read FCurrent;
494475 function GetEnumerator: TGIndicedEnumerator;
495476 end;
496477
497478 TCsvAnsiStringFieldEnumerator = class(specialize TGIndicedEnumerator<AnsiString, AnsiString>)
498479 private
499480 FCurrPos: Integer;
500481 FSeparator: Char;
501482 public
502483 constructor Create(aString: AnsiString; Separator: AnsiChar); overload;
503484 function MoveNext: Boolean; override;
504485 end;
505486
506487 implementation
507488
508489 { TGIndicedEnumerator }
509490
510491 constructor TGIndicedEnumerator.Create(aCntnr: TContainer);
511492 begin
512493 FCntnr := aCntnr;
513494 end;
514495
515496 function TGIndicedEnumerator.GetEnumerator: TGIndicedEnumerator;
516497 begin
517498 Result := Self;
518499 end;
519500
520501 { TCsvAnsiStringFieldEnumerator }
521502
522503 constructor TCsvAnsiStringFieldEnumerator.Create(aString: AnsiString; Separator: AnsiChar);
523504 begin
524505 inherited Create(aString);
525506 FCurrPos := 0;
526507 FSeparator := Separator;
527508 FCurrent.Ind := -1;
528509 end;
529510
530511 function TCsvAnsiStringFieldEnumerator.MoveNext: Boolean;
531512 begin
532513 if FCurrPos > Length(FCntnr) then exit(False);
533514 // Premier élément
534515 Inc(FCurrent.ind);
535516 FCurrent.Value := '';
536517 Inc(FCurrPos);
537518 while (FCurrPos <= Length(FCntnr)) and (FCntnr[FCurrPos] <> FSeparator) do
538519 begin
539520 FCurrent.Value += FCntnr[FCurrPos];
540521 Inc(FCurrPos);
541522 end;
542523 Result := True;
543524 end;
544525
545526 end.
546527```
547528
548529Un 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").
549530
550531Dans 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.
551532
552533L'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 :
553534```
554535#!/usr/bin/instantfpc
555536
556537 // (see the Instantfpc doc)
557538
558539 {$mode objfpc}{$H+}
559540
560541 uses
561542 SysUtils,
562543 IndicedEnumerators;
563544
564545 var
565546 IndStr: TCsvAnsiStringFieldEnumerator.TIndicedValue;
566547
567548 begin
568549 // énumérateur obtenu directement à partir de la classe elle-même, la libération de l'énumérateur
569550 // est garantie en sortie de la boucle for .. in
570551 for IndStr in TCsvAnsiStringFieldEnumerator.Create(ParamStr(1), ';') do
571552 WriteLn(Format('%d : "%s"', [IndStr.Ind, IndStr.Value]));
572553 end.
573554```
574555
575556Appel du script InstantFPC en ligne de commande (nom du fichier source pascal avec des droit d'exécution sous Linux + un paramètre)
576557```
577558./TestStringFieldEnums.pas "small;string;with;separated;fields"
578559```
579560
580561Cette 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.
581562
582563Enfin, 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).
583564
584565# Référence
585566
586567 * <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
587568
588569---
589570*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