Difference between revisions of "for-in loop"
Line 58: | Line 58: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | There are only 2 things required for the enumerator: a MoveNext method which asks the enumerator to step forward and a Current property which can return any appropriate type. | + | There are only 2 things required for the enumerator class: a <b>MoveNext</b> method which asks the enumerator to step forward and a '''Current''' property which can return any appropriate type. |
Thereafter you need to add the magic <b>GetEnumerator</b> method to the container class which returns an <b>enumerator</b> instance. | Thereafter you need to add the magic <b>GetEnumerator</b> method to the container class which returns an <b>enumerator</b> instance. |
Revision as of 21:11, 24 January 2013
│
English (en) │
français (fr) │
日本語 (ja) │
русский (ru) │
"for-in" loop exists in delphi starting from 2005 version. It is implemented now in FPC 2.4.2.
Delphi and FPC implementation
A for in loop has the following syntax:
String loop
procedure StringLoop(S: String);
var
C: Char;
begin
for C in S do
DoSomething(C);
end;
Array loop
procedure ArrayLoop(A: Array of Byte);
var
B: Byte;
begin
for B in A do
DoSomething(B);
end;
Set loop
type
TColor = (cRed, cGren, cBlue);
TColors = set of TColor;
procedure SetLoop(Colors: TColors);
var
Color: TColor;
begin
for Color in Colors do
DoSomething(Color);
end;
Traversing container
To traverse a container class you need to add an enumerator for it. An Enumerator is a class structured according to the following template:
TSomeEnumerator = class
public
function MoveNext: Boolean;
property Current: TSomeType;
end;
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.
Thereafter you need to add the magic GetEnumerator method to the container class which returns an enumerator instance.
For example:
type
TEnumerableTree = class;
TTreeEnumerator = class
private
FTree: TEnumerableTree;
FCurrent: TNode;
public
constructor Create(ATree: TEnumerableTree);
function MoveNext: Boolean;
property Current: TNode read FCurrent;
end;
TEnumerableTree = class
public
function GetEnumerator: TTreeEnumerator;
end;
constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
begin
inherited Create;
FTree := ATree;
FCurrent := nil;
end;
function TTreeEnumerator.MoveNext: Boolean;
begin
// some logic to get the next node from a tree
if FCurrent = nil then
FCurrent := FTree.GetFirstNode
else
FCurrent := FTree.GetNextNode(FCurrent);
Result := FCurrent <> nil;
end;
function TEnumerableTree.GetEnumerator: TTreeEnumerator;
begin
Result := TTreeEnumerator.Create(Self);
end;
After this you are able to execute the following code:
procedure TreeLoop(ATree: TEnumerableTree);
var
ANode: TNode;
begin
for ANode in ATree do
DoSomething(ANode);
end;
Of course enumerator support is built into the basic classes: TList, TStrings, TCollection, TComponent, ...
It is also possible to make any class enumerable if you implement the following interface in your enumerable container class:
IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;
Where IEnumerator is declared as:
IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;
Multiple enumerators for one class
You can add additional enumerators to classes, objects and records.
Example for adding an enumerator to traverse the tree in reverse order:
type
TEnumerableTree = class;
TTreeEnumerator = class
...for traversing in order, see above...
end;
TTreeReverseEnumerator = class
private
FTree: TEnumerableTree;
FCurrent: TNode;
public
constructor Create(ATree: TEnumerableTree);
function MoveNext: Boolean;
property Current: TNode read FCurrent;
function GetEnumerator: TTreeReverseEnumerator; // returns itself
end;
TEnumerableTree = class
public
function GetEnumerator: TTreeEnumerator;
function GetReverseEnumerator: TTreeReverseEnumerator;
end;
...see above for implementation of the TTreeEnumerator...
constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
begin
inherited Create;
FTree := ATree;
end;
function TTreeReverseEnumerator.MoveNext: Boolean;
begin
// some logic to get the next node from a tree in reverse order
if FCurrent = nil then
FCurrent := FTree.GetLastNode
else
FCurrent := FTree.GetPrevNode(FCurrent);
Result := FCurrent <> nil;
end;
function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
begin
Result := Self;
end;
function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
begin
Result := TTreeReverseEnumerator.Create(Self);
end;
After this you are able to execute the next code:
procedure TreeLoop(ATree: TEnumerableTree);
var
ANode: TNode;
begin
for ANode in ATree.GetReverseEnumerator do
DoSomething(ANode);
end;
FPC extensions
The following examples are not supported by Delphi, and implemented in FPC only.
Traverse enumerations and ranges
In Delphi, it is not possible to traverse either enumerated types or range types, whereas in Free Pascal we can write the following:
type
TColor = (clRed, clBlue, clBlack);
TRange = 'a'..'z';
var
Color: TColor;
ch: Char;
begin
for Color in TColor do
DoSomething(Color);
for ch in TRange do
DoSomethingOther(ch);
end.
Declaring enumerators
It is also not possible in Delphi to add an enumerator without modifying the class, as well as add an enumerator to the non-class/object/record/interface type. It is possible in FPC by adding new operator type Enumerator. Like in the next example:
type
TMyRecord = record F1: Integer; F2: array of TMyType; end;
TMyArrayEnumerator = class
private
function GetCurrent: TMyType;
public
constructor Create(const A: TMyRecord);
property Current: TMyType read GetCurrent;
function MoveNext: Boolean;
end;
// This is new built-in operator.
operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
begin
Result := TMyArrayEnumerator.Create(A);
end;
var
A: MyRecord;
V: TMyType
begin
for V in A do
DoSomething(V);
end.
As a particularly useful example, the above extension would allow to traverse UTF-8 strings efficiently:
type
TUTF8StringEnumerator = class
private
FByteIndex: Integer;
FCharIndex: Integer;
function GetCurrent: UTF8Char;
public
constructor Create(const A: UTF8String);
property Current: UTF8Char read GetCurrent;
function MoveNext: Boolean;
end;
operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
begin
Result := TUTF8StringEnumerator.Create(A);
end;
var
s: UTF8String;
ch: UTF8Char;
i: Integer;
begin
// This requires O(N^2) operations
for i := 1 to Length(s) do
DoSomething(ch[i]);
// This requires only O(N) operations
for ch in s do
DoSomething(ch);
end.
Using any identifiers instead of builtin MoveNext and Current
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 solved this by using 'enumerator MoveNext' and 'enumerator Current' modifiers. Like in the next example:
type
{ TMyListEnumerator }
TMyListEnumerator = object
private
FCurrent: Integer;
public
constructor Create;
destructor Destroy;
function StepNext: Boolean; enumerator MoveNext;
property Value: Integer read FCurrent; enumerator Current;
end;
TMyList = class
end;
{ TMyListEnumerator }
constructor TMyListEnumerator.Create;
begin
FCurrent := 0;
end;
destructor TMyListEnumerator.Destroy;
begin
inherited;
end;
function TMyListEnumerator.StepNext: Boolean;
begin
inc(FCurrent);
Result := FCurrent <= 3;
end;
operator enumerator (AList: TMyList): TMyListEnumerator;
begin
Result.Create;
end;
var
List: TMyList;
i: integer;
begin
List := TMyList.Create;
for i in List do
WriteLn(i);
List.Free;
end.
Proposed extensions
Get enumerator Position if available
It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as current index, may be useful:
type
TUTF8StringEnumerator = class
private
FByteIndex: Integer;
FCharIndex: Integer;
public
constructor Create(const A: UTF8String);
function Current: UTF8Char;
function CurrentIndex: Integer;
function MoveNext: Boolean;
end;
operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
begin
Result := TUTF8String.Create(A);
end;
var
s: UTF8String;
ch: UTF8Char;
i: Integer;
begin
// Inefficient, as discussed above
for i := 1 to Length(s) do
Writeln(i, ': ', ch[i]);
// Ok, but ugly
i := 1;
for ch in s do begin
Writeln(i, ': ', ch);
Inc(i);
end;
// Proposed extension
for ch in s index i do
Writeln(i, ': ', ch);
end.
Note that index might return arbitrary type, not necessarily integer. For example, in the case of tree traversal, the index might return an array of nodes from on the path from the tree root to the current node.