for-in loop: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: for-in loop
| Old | New | Line | |
|---|---|---|---|
| 1 | + | This iterates over a collection, but where a [[For|basic for-loop]] uses a numerical index counter, a for-in loop instead retrieves collection elements into the counter variable for immediate use. For-in works on strings, arrays, sets, and any other custom collection that implements the required iterators. Looping over an empty collection does nothing. The counter variable can not be modified inside the loop. | |
| 2 | + | ||
| 3 | + | The **for-in loop** construct is supported in Delphi from Delphi 2005 onwards. It was implemented in FPC 2.4.2. | |
| 4 | + | ||
| 5 | + | The official documentation is here: [Reference guide chapter 13](https://www.freepascal.org/docs-html/ref/refsu58.html) | |
| 6 | + | ||
| 7 | + | ## Delphi and FPC implementation | |
| 8 | + | ||
| 9 | + | A **for in** loop has the following syntax: | |
| 10 | + | ||
| 11 | + | ### String loop | |
| 12 | + | ||
| 13 | + | ``` pascal | |
| 14 | + | procedure StringLoop(S: String); | |
| 15 | + | var | |
| 16 | + | C: Char; | |
| 17 | + | begin | |
| 18 | + | for C in S do | |
| 19 | + | DoSomething(C); | |
| 20 | + | end; | |
| 21 | + | ``` | |
| 22 | + | ||
| 23 | + | ### Array loop | |
| 24 | + | ||
| 25 | + | ``` pascal | |
| 26 | + | procedure ArrayLoop(A: Array of Byte); | |
| 27 | + | var | |
| 28 | + | B: Byte; | |
| 29 | + | begin | |
| 30 | + | for B in A do | |
| 31 | + | DoSomething(B); | |
| 32 | + | end; | |
| 33 | + | ``` | |
| 34 | + | ||
| 35 | + | ### Set loop | |
| 36 | + | ||
| 37 | + | ``` pascal | |
| 38 | + | type | |
| 39 | + | TColor = (cRed, cGren, cBlue); | |
| 40 | + | TColors = set of TColor; | |
| 41 | + | procedure SetLoop(Colors: TColors); | |
| 42 | + | var | |
| 43 | + | Color: TColor; | |
| 44 | + | begin | |
| 45 | + | for Color in Colors do | |
| 46 | + | DoSomething(Color); | |
| 47 | + | end; | |
| 48 | + | ``` | |
| 49 | + | ||
| 50 | + | ### Loop variables are temporary copies of the container value | |
| 51 | + | ||
| 52 | + | The loop variable in a for-in loop is a [copy of the value that is stored inside a container over which the for-in loop is performed](https://stackoverflow.com/questions/18575736/delphi-dynamic-array-iteration-and-record-copyingsimple). | |
| 53 | + | ||
| 54 | + | ``` pascal | |
| 55 | + | program tempcopy; | |
| 56 | + | ||
| 57 | + | {$IFDEF FPC} | |
| 58 | + | {$mode Delphi} | |
| 59 | + | {$ENDIF} | |
| 60 | + | ||
| 61 | + | uses SysUtils; | |
| 62 | + | ||
| 63 | + | type | |
| 64 | + | PointerAddress = NativeUInt; | |
| 65 | + | ||
| 66 | + | var | |
| 67 | + | pIntArr: Pointer; | |
| 68 | + | IntArr: Array of Integer; | |
| 69 | + | ||
| 70 | + | procedure ArrayLoop(const AArr: Array of Integer); | |
| 71 | + | var | |
| 72 | + | i: Integer; | |
| 73 | + | begin | |
| 74 | + | pIntArr := @AArr; | |
| 75 | + | writeln('Argument: ', PointerAddress(pIntArr)); // <-- same memory address as Item 1 | |
| 76 | + | pIntArr := @i; | |
| 77 | + | writeln('Local loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i | |
| 78 | + | for i in AArr do | |
| 79 | + | begin | |
| 80 | + | pIntArr := @i; | |
| 81 | + | writeln('Loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i | |
| 82 | + | end; | |
| 83 | + | end; | |
| 84 | + | ||
| 85 | + | begin | |
| 86 | + | // allow 3 items in the array | |
| 87 | + | SetLength(IntArr, 3); | |
| 88 | + | ||
| 89 | + | pIntArr := @pIntArr; | |
| 90 | + | writeln('global pIntArr address: ', PointerAddress(pIntArr)); | |
| 91 | + | ||
| 92 | + | // IntArr is separate variable that points to first element | |
| 93 | + | pIntArr := @IntArr; | |
| 94 | + | writeln('global IntArr address: ', PointerAddress(pIntArr)); | |
| 95 | + | pIntArr := @IntArr; | |
| 96 | + | writeln('IntArr points to: ', PointerAddress(pIntArr^)); | |
| 97 | + | ||
| 98 | + | // addresses of the single items | |
| 99 | + | pIntArr := @IntArr[0]; | |
| 100 | + | writeln('Item 1: ', PointerAddress(pIntArr)); // <-- memory address of the first item | |
| 101 | + | pIntArr := @IntArr[1]; | |
| 102 | + | writeln('Item 2: ', PointerAddress(pIntArr)); | |
| 103 | + | pIntArr := @IntArr[2]; | |
| 104 | + | writeln('Item 3: ', PointerAddress(pIntArr)); | |
| 105 | + | ||
| 106 | + | writeln('array loop'); | |
| 107 | + | ArrayLoop(IntArr); | |
| 108 | + | end. | |
| 109 | + | ``` | |
| 110 | + | ||
| 111 | + | ### Traversing container | |
| 112 | + | ||
| 113 | + | To traverse a container class you need to add an enumerator for it. An Enumerator is a class structured according to the following template: | |
| 114 | + | ||
| 115 | + | ``` pascal | |
| 116 | + | TSomeEnumerator = class | |
| 117 | + | public | |
| 118 | + | function MoveNext: Boolean; | |
| 119 | + | property Current: TSomeType; | |
| 120 | + | end; | |
| 121 | + | ``` | |
| 122 | + | ||
| 123 | + | There are only 2 things required for the enumerator class: a MoveNext method which asks the enumerator to step forward and a **Current** property which can return any appropriate type. | |
| 124 | + | ||
| 125 | + | Thereafter you need to add the magic GetEnumerator method to the container class which returns an enumerator instance. | |
| 126 | + | ||
| 127 | + | For example: | |
| 128 | + | ||
| 129 | + | ``` pascal | |
| 130 | + | type | |
| 131 | + | TEnumerableTree = class; | |
| 132 | + | ||
| 133 | + | TTreeEnumerator = class | |
| 134 | + | private | |
| 135 | + | FTree: TEnumerableTree; | |
| 136 | + | FCurrent: TNode; | |
| 137 | + | public | |
| 138 | + | constructor Create(ATree: TEnumerableTree); | |
| 139 | + | function MoveNext: Boolean; | |
| 140 | + | property Current: TNode read FCurrent; | |
| 141 | + | end; | |
| 142 | + | ||
| 143 | + | TEnumerableTree = class | |
| 144 | + | public | |
| 145 | + | function GetEnumerator: TTreeEnumerator; | |
| 146 | + | end; | |
| 147 | + | ||
| 148 | + | constructor TTreeEnumerator.Create(ATree: TEnumerableTree); | |
| 149 | + | begin | |
| 150 | + | inherited Create; | |
| 151 | + | FTree := ATree; | |
| 152 | + | FCurrent := nil; | |
| 153 | + | end; | |
| 154 | + | ||
| 155 | + | function TTreeEnumerator.MoveNext: Boolean; | |
| 156 | + | begin | |
| 157 | + | // some logic to get the next node from a tree | |
| 158 | + | if FCurrent = nil then | |
| 159 | + | FCurrent := FTree.GetFirstNode | |
| 160 | + | else | |
| 161 | + | FCurrent := FTree.GetNextNode(FCurrent); | |
| 162 | + | Result := FCurrent <> nil; | |
| 163 | + | end; | |
| 164 | + | ||
| 165 | + | function TEnumerableTree.GetEnumerator: TTreeEnumerator; | |
| 166 | + | begin | |
| 167 | + | Result := TTreeEnumerator.Create(Self); | |
| 168 | + | // Note: the Result is automatically freed by the compiler after the loop. | |
| 169 | + | end; | |
| 170 | + | ``` | |
| 171 | + | ||
| 172 | + | After this you are able to execute the following code: | |
| 173 | + | ||
| 174 | + | ``` pascal | |
| 175 | + | procedure TreeLoop(ATree: TEnumerableTree); | |
| 176 | + | var | |
| 177 | + | ANode: TNode; | |
| 178 | + | begin | |
| 179 | + | for ANode in ATree do | |
| 180 | + | DoSomething(ANode); | |
| 181 | + | end; | |
| 182 | + | ``` | |
| 183 | + | ||
| 184 | + | You will find that several basic classes (such as TList, TStrings, TCollection, TComponent ...) already have built-in enumerator support. | |
| 185 | + | ||
| 186 | + | It is also possible to make any class enumerable if you implement the following interface in your enumerable container class: | |
| 187 | + | ||
| 188 | + | ``` pascal | |
| 189 | + | IEnumerable = interface(IInterface) | |
| 190 | + | function GetEnumerator: IEnumerator; | |
| 191 | + | end; | |
| 192 | + | ``` | |
| 193 | + | ||
| 194 | + | Where IEnumerator is declared as: | |
| 195 | + | ||
| 196 | + | ``` pascal | |
| 197 | + | IEnumerator = interface(IInterface) | |
| 198 | + | function GetCurrent: TObject; | |
| 199 | + | function MoveNext: Boolean; | |
| 200 | + | procedure Reset; | |
| 201 | + | property Current: TObject read GetCurrent; | |
| 202 | + | end; | |
| 203 | + | ``` | |
| 204 | + | ||
| 205 | + | ### Multiple enumerators for one class | |
| 206 | + | ||
| 207 | + | You can add additional enumerators to classes, objects and records. | |
| 208 | + | ||
| 209 | + | Here is an example of adding an enumerator which traverses a TEnumerableTree in reverse order: | |
| 210 | + | ||
| 211 | + | ``` pascal | |
| 212 | + | type | |
| 213 | + | TEnumerableTree = class; | |
| 214 | + | ||
| 215 | + | TTreeEnumerator = class | |
| 216 | + | ...for traversing in order, see above... | |
| 217 | + | end; | |
| 218 | + | ||
| 219 | + | TTreeReverseEnumerator = class | |
| 220 | + | private | |
| 221 | + | FTree: TEnumerableTree; | |
| 222 | + | FCurrent: TNode; | |
| 223 | + | public | |
| 224 | + | constructor Create(ATree: TEnumerableTree); | |
| 225 | + | function MoveNext: Boolean; | |
| 226 | + | property Current: TNode read FCurrent; | |
| 227 | + | function GetEnumerator: TTreeReverseEnumerator; // returns itself | |
| 228 | + | end; | |
| 229 | + | ||
| 230 | + | TEnumerableTree = class | |
| 231 | + | public | |
| 232 | + | function GetEnumerator: TTreeEnumerator; | |
| 233 | + | function GetReverseEnumerator: TTreeReverseEnumerator; | |
| 234 | + | end; | |
| 235 | + | ||
| 236 | + | ...see above for an implementation of the TTreeEnumerator... | |
| 237 | + | ||
| 238 | + | constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree); | |
| 239 | + | begin | |
| 240 | + | inherited Create; | |
| 241 | + | FTree := ATree; | |
| 242 | + | end; | |
| 243 | + | ||
| 244 | + | function TTreeReverseEnumerator.MoveNext: Boolean; | |
| 245 | + | begin | |
| 246 | + | // some logic to get the next node from a tree in reverse order | |
| 247 | + | if FCurrent = nil then | |
| 248 | + | FCurrent := FTree.GetLastNode | |
| 249 | + | else | |
| 250 | + | FCurrent := FTree.GetPrevNode(FCurrent); | |
| 251 | + | Result := FCurrent <> nil; | |
| 252 | + | end; | |
| 253 | + | ||
| 254 | + | function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator; | |
| 255 | + | begin | |
| 256 | + | Result := Self; | |
| 257 | + | end; | |
| 258 | + | ||
| 259 | + | function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator; | |
| 260 | + | begin | |
| 261 | + | Result := TTreeReverseEnumerator.Create(Self); | |
| 262 | + | // Note: the Result is freed automatically by the compiler after the loop. | |
| 263 | + | end; | |
| 264 | + | ``` | |
| 265 | + | ||
| 266 | + | After this you are able to execute the following code: | |
| 267 | + | ||
| 268 | + | ``` pascal | |
| 269 | + | procedure TreeLoop(ATree: TEnumerableTree); | |
| 270 | + | var | |
| 271 | + | ANode: TNode; | |
| 272 | + | begin | |
| 273 | + | for ANode in ATree.GetReverseEnumerator do | |
| 274 | + | DoSomething(ANode); | |
| 275 | + | end; | |
| 276 | + | ``` | |
| 277 | + | ||
| 278 | + | ## FPC extensions | |
| 279 | + | ||
| 280 | + | The following code examples illustrate constructs that are implemented only by FPC, constructs which are not supported by Delphi. | |
| 281 | + | ||
| 282 | + | ### Traversing enumeration and subrange types | |
| 283 | + | ||
| 284 | + | In Delphi, it is [not possible to traverse either enumerated types](http://qc.embarcadero.com/wc/qcmain.aspx?d=106212) or subrange types, whereas in Free Pascal we can write the following: | |
| 285 | + | ||
| 286 | + | ``` pascal | |
| 287 | + | type | |
| 288 | + | TColor = (clRed, clBlue, clBlack); | |
| 289 | + | TRange = 'a'..'z'; | |
| 290 | + | var | |
| 291 | + | Color: TColor; | |
| 292 | + | ch: Char; | |
| 293 | + | begin | |
| 294 | + | for Color in TColor do | |
| 295 | + | DoSomething(Color); | |
| 296 | + | for ch in TRange do | |
| 297 | + | DoSomethingOther(ch); | |
| 298 | + | end. | |
| 299 | + | ``` | |
| 300 | + | ||
| 301 | + | An example of this can further be demonstrated by the following code taken from bugreport 0029147, where the type system is disabled because a hardcast is used on a value that is not necessarily part of the enum value: | |
| 302 | + | ||
| 303 | + | ``` pascal | |
| 304 | + | type | |
| 305 | + | TSomeEnums = (One, Two, Three); | |
| 306 | + | ||
| 307 | + | resourcestring | |
| 308 | + | SOne = 'One ape'; | |
| 309 | + | STwo = 'Two apes'; | |
| 310 | + | SThree = 'Three apes'; | |
| 311 | + | ||
| 312 | + | const | |
| 313 | + | SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = ( | |
| 314 | + | SOne, STwo, SThree); | |
| 315 | + | ||
| 316 | + | var | |
| 317 | + | i: Integer; | |
| 318 | + | SE: TSomeEnums; | |
| 319 | + | ||
| 320 | + | begin | |
| 321 | + | for i := 0 to 4 do begin | |
| 322 | + | SE := TSomeEnums(i); // hardcast. i can be higher than 2, but the type system is now disabled | |
| 323 | + | WriteLn(SSomeEnumStrings[SE]); | |
| 324 | + | end; | |
| 325 | + | end. | |
| 326 | + | ``` | |
| 327 | + | ||
| 328 | + | The programmer instructs the compiler that i is of the enum type and the compiler will not check any further, assuming that the programmer knows best.\ | |
| 329 | + | Of course the programmer is wrong and the compiler would have known better...\ | |
| 330 | + | By traversing the enumerated type with **for in do** the hard cast is superfluous and the code becomes type safe: | |
| 331 | + | ||
| 332 | + | ``` pascal | |
| 333 | + | type | |
| 334 | + | TSomeEnums = (One, Two, Three); | |
| 335 | + | ||
| 336 | + | resourcestring | |
| 337 | + | SOne = 'One ape'; | |
| 338 | + | STwo = 'Two apes'; | |
| 339 | + | SThree = 'Three apes'; | |
| 340 | + | ||
| 341 | + | const | |
| 342 | + | SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = ( | |
| 343 | + | SOne, STwo, SThree); | |
| 344 | + | ||
| 345 | + | var | |
| 346 | + | SE: TSomeEnums; | |
| 347 | + | begin | |
| 348 | + | for SE in TSomeEnums do | |
| 349 | + | WriteLn(SSomeEnumStrings[SE]); | |
| 350 | + | end. | |
| 351 | + | ``` | |
| 352 | + | ||
| 353 | + | ### Declaring enumerators | |
| 354 | + | ||
| 355 | + | It is also not possible in Delphi to add an enumerator without modifying the class, nor to add an enumerator to the non-class/object/record/interface type. FPC makes this possible using the new syntax operator type Enumerator. As in the following example: | |
| 356 | + | ||
| 357 | + | ``` pascal | |
| 358 | + | type | |
| 359 | + | TMyRecord = record F1: Integer; F2: array of TMyType; end; | |
| 360 | + | TMyArrayEnumerator = class | |
| 361 | + | private | |
| 362 | + | function GetCurrent: TMyType; | |
| 363 | + | public | |
| 364 | + | constructor Create(const A: TMyRecord); | |
| 365 | + | property Current: TMyType read GetCurrent; | |
| 366 | + | function MoveNext: Boolean; | |
| 367 | + | end; | |
| 368 | + | ||
| 369 | + | // This is new built-in operator. | |
| 370 | + | operator Enumerator(const A: TMyRecord): TMyArrayEnumerator; | |
| 371 | + | begin | |
| 372 | + | Result := TMyArrayEnumerator.Create(A); | |
| 373 | + | end; | |
| 374 | + | ||
| 375 | + | var | |
| 376 | + | A: MyRecord; | |
| 377 | + | V: TMyType | |
| 378 | + | begin | |
| 379 | + | for V in A do | |
| 380 | + | DoSomething(V); | |
| 381 | + | end. | |
| 382 | + | ``` | |
| 383 | + | ||
| 384 | + | #### Traversing UTF-8 strings | |
| 385 | + | ||
| 386 | + | As a particularly useful example, the above extension allows very efficient traversal of UTF-8 strings: | |
| 387 | + | ||
| 388 | + | ``` pascal | |
| 389 | + | uses | |
| 390 | + | LazUTF8; | |
| 391 | + | interface | |
| 392 | + | type | |
| 393 | + | { TUTF8StringAsStringEnumerator | |
| 394 | + | Traversing UTF8 codepoints as strings is useful when you want to know the | |
| 395 | + | exact encoding of the UTF8 character or if you like to use string | |
| 396 | + | constants in your code. | |
| 397 | + | For security reasons you should use the codepoints values (cardinals) instead. | |
| 398 | + | If speed matters, don't use enumerators. Instead use the PChar directly as | |
| 399 | + | shown in the MoveNext method and read about UTF8. It has some interesting features. } | |
| 400 | + | ||
| 401 | + | TUTF8StringAsStringEnumerator = class | |
| 402 | + | private | |
| 403 | + | fCurrent: UTF8String; | |
| 404 | + | fCurrentPos, fEndPos: PChar; | |
| 405 | + | function GetCurrent: UTF8String; | |
| 406 | + | public | |
| 407 | + | constructor Create(const A: UTF8String); | |
| 408 | + | property Current: UTF8String read GetCurrent; | |
| 409 | + | function MoveNext: Boolean; | |
| 410 | + | end; | |
| 411 | + | ||
| 412 | + | operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator; | |
| 413 | + | ||
| 414 | + | var | |
| 415 | + | Form1: TForm1; | |
| 416 | + | ||
| 417 | + | implementation | |
| 418 | + | ||
| 419 | + | operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator; | |
| 420 | + | begin | |
| 421 | + | Result := TUTF8StringAsStringEnumerator.Create(A); | |
| 422 | + | end; | |
| 423 | + | ||
| 424 | + | { TUTF8StringAsStringEnumerator } | |
| 425 | + | ||
| 426 | + | function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String; | |
| 427 | + | begin | |
| 428 | + | Result:=fCurrent; | |
| 429 | + | end; | |
| 430 | + | ||
| 431 | + | constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String); | |
| 432 | + | begin | |
| 433 | + | fCurrentPos:=PChar(A); // Note: if A='' then PChar(A) returns a pointer to a #0 string | |
| 434 | + | fEndPos:=fCurrentPos+length(A); | |
| 435 | + | end; | |
| 436 | + | ||
| 437 | + | function TUTF8StringAsStringEnumerator.MoveNext: Boolean; | |
| 438 | + | var | |
| 439 | + | l: Integer; | |
| 440 | + | begin | |
| 441 | + | if fCurrentPos<fEndPos then | |
| 442 | + | begin | |
| 443 | + | l:=UTF8CharacterLength(fCurrentPos); | |
| 444 | + | SetLength(fCurrent,l); | |
| 445 | + | Move(fCurrentPos^,fCurrent[1],l); | |
| 446 | + | inc(fCurrentPos,l); | |
| 447 | + | Result:=true; | |
| 448 | + | end else | |
| 449 | + | Result:=false; | |
| 450 | + | end; | |
| 451 | + | ||
| 452 | + | { TForm1 } | |
| 453 | + | ||
| 454 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 455 | + | var | |
| 456 | + | s, ch: UTF8String; | |
| 457 | + | i: SizeInt; | |
| 458 | + | begin | |
| 459 | + | s:='mäßig'; | |
| 460 | + | ||
| 461 | + | // using UTF8Length and UTF8Copy this way is slow, requiring O(n)^2 | |
| 462 | + | for i:=1 to UTF8Length(s) do | |
| 463 | + | writeln('ch=',UTF8Copy(s,i,1)); | |
| 464 | + | ||
| 465 | + | // using the above enumerator is shorter and quite fast, requiring O(n) | |
| 466 | + | for ch in s do | |
| 467 | + | writeln('ch=',ch); | |
| 468 | + | end; | |
| 469 | + | ``` | |
| 470 | + | ||
| 471 | + | ### Using any identifiers instead of builtin MoveNext and Current | |
| 472 | + | ||
| 473 | + | In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is enabled by the use of the **enumerator** modifier, with the syntax 'enumerator MoveNext;' and 'enumerator Current;' modifiers. As in the following example: | |
| 474 | + | ||
| 475 | + | ``` pascal | |
| 476 | + | type | |
| 477 | + | { TMyListEnumerator } | |
| 478 | + | ||
| 479 | + | TMyListEnumerator = object | |
| 480 | + | private | |
| 481 | + | FCurrent: Integer; | |
| 482 | + | public | |
| 483 | + | constructor Create; | |
| 484 | + | destructor Destroy; | |
| 485 | + | function StepNext: Boolean; enumerator MoveNext; | |
| 486 | + | property Value: Integer read FCurrent; enumerator Current; | |
| 487 | + | end; | |
| 488 | + | ||
| 489 | + | TMyList = class | |
| 490 | + | end; | |
| 491 | + | ||
| 492 | + | { TMyListEnumerator } | |
| 493 | + | ||
| 494 | + | constructor TMyListEnumerator.Create; | |
| 495 | + | begin | |
| 496 | + | FCurrent := 0; | |
| 497 | + | end; | |
| 498 | + | ||
| 499 | + | destructor TMyListEnumerator.Destroy; | |
| 500 | + | begin | |
| 501 | + | inherited; | |
| 502 | + | end; | |
| 503 | + | ||
| 504 | + | function TMyListEnumerator.StepNext: Boolean; | |
| 505 | + | begin | |
| 506 | + | inc(FCurrent); | |
| 507 | + | Result := FCurrent <= 3; | |
| 508 | + | end; | |
| 509 | + | ||
| 510 | + | operator enumerator (AList: TMyList): TMyListEnumerator; | |
| 511 | + | begin | |
| 512 | + | Result.Create; | |
| 513 | + | end; | |
| 514 | + | ||
| 515 | + | var | |
| 516 | + | List: TMyList; | |
| 517 | + | i: integer; | |
| 518 | + | begin | |
| 519 | + | List := TMyList.Create; | |
| 520 | + | for i in List do | |
| 521 | + | WriteLn(i); | |
| 522 | + | List.Free; | |
| 523 | + | end. | |
| 524 | + | ``` | |
| 525 | + | ||
| 526 | + | ## Proposed extensions | |
| 527 | + | ||
| 528 | + | ### Get enumerator Position if available | |
| 529 | + | ||
| 530 | + | It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as the current index, might be useful: | |
| 531 | + | ||
| 532 | + | ``` pascal | |
| 533 | + | type | |
| 534 | + | TUTF8StringEnumerator = class | |
| 535 | + | private | |
| 536 | + | FByteIndex: Integer; | |
| 537 | + | FCharIndex: Integer; | |
| 538 | + | public | |
| 539 | + | constructor Create(const A: UTF8String); | |
| 540 | + | function Current: UTF8Char; | |
| 541 | + | function CurrentIndex: Integer; | |
| 542 | + | function MoveNext: Boolean; | |
| 543 | + | end; | |
| 544 | + | ||
| 545 | + | operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator; | |
| 546 | + | begin | |
| 547 | + | Result := TUTF8String.Create(A); | |
| 548 | + | end; | |
| 549 | + | ||
| 550 | + | var | |
| 551 | + | s: UTF8String; | |
| 552 | + | ch: UTF8Char; | |
| 553 | + | i: Integer; | |
| 554 | + | begin | |
| 555 | + | ||
| 556 | + | // Inefficient, as discussed above | |
| 557 | + | for i := 1 to Length(s) do | |
| 558 | + | Writeln(i, ': ', ch[i]); | |
| 559 | + | ||
| 560 | + | // Ok, but ugly | |
| 561 | + | i := 1; | |
| 562 | + | for ch in s do begin | |
| 563 | + | Writeln(i, ': ', ch); | |
| 564 | + | Inc(i); | |
| 565 | + | end; | |
| 566 | + | ||
| 567 | + | // Proposed extension | |
| 568 | + | for ch in s index i do | |
| 569 | + | Writeln(i, ': ', ch); | |
| 570 | + | ||
| 571 | + | // Proposed extension for traversing backwards (equivalent to downto) | |
| 572 | + | for ch in reverse s do | |
| 573 | + | Writeln(i, ': ', ch); | |
| 574 | + | ||
| 575 | + | // With proposed index extension | |
| 576 | + | for ch in reverse s index i do | |
| 577 | + | Writeln(i, ': ', ch); | |
| 578 | + | end. | |
| 579 | + | ``` | |
| 580 | + | ||
| 581 | + | Note that index could be designed to return an arbitrary type (i. e. not necessarily an integer). For example, in the case of tree traversal, the index might return an array of nodes describing the path from the tree root to the current node. | |
| 582 | + | ||
| 583 | + | ## Reference | |
| 584 | + | ||
| 585 | + | - <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html> | |
| 586 | + | ||
| 587 | + | --- | |
| 588 | + | *From the Free Pascal wiki, page "for-in loop", as it stood on 2021-04-10 after a change by Bi0T1N, out of the wiki's dump of 2024-05-26. 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).* |
