for-in loop/ru

From Lazarus wiki
Revision as of 22:03, 14 September 2011 by Mr.Smart (talk | contribs)
Jump to navigationJump to search

English (en) français (fr) русский (ru)

"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция доступна сейчас с версии fpc 2.4.2.

Delphi и FPC реализация

Она имеет следующий синтаксис:

Цикл применимый к строкам

<delphi> procedure StringLoop(S: String); var

 C: Char;

begin

 for C in S do
   DoSomething(C);

end; </delphi>

Цикл применимый к массиву

<delphi> procedure ArrayLoop(A: Array of Byte); var

 B: Byte;

begin

 for B in A do
   DoSomething(B);

end; </delphi>

Цикл применимый к множеству

<delphi> 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; </delphi>

Применение к классам контейнерам

Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:

<delphi> TSomeEnumerator = class public

 function MoveNext: Boolean;
 property Current: TSomeType;

end; </delphi>

Для реализации перечислителя необходимы: метод MoveNext, который увеличивает позицию элемента перечисления и свойство Current, в котором возвращается выбранный вами тип данных.

Следующим шагом необходимо добавить к классу контейнеру специального метода GetEnumerator, который будет возвращать экземпляр перечислителя.

Пример: <delphi> 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

 // получение следующего узла дерева
 if FCurrent = nil then
   FCurrent := FTree.GetFirstNode
 else
   FCurrent := FTree.GetNextNode(FCurrent);
 Result := FCurrent <> FTree.GetLastNode;

end;

function TEnumerableTree.GetEnumerator: TTreeEnumerator; begin

 Result := TTreeEnumerator.Create(Self);

end;

</delphi>

После этого вы можете использовать следующий код:

<delphi> procedure TreeLoop(ATree: TEnumerableTree); var

 ANode: TNode;

begin

 for ANode in ATree do
   DoSomething(ANode);

end; </delphi>

Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...

Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс: <delphi>

 IEnumerable = interface(IInterface)
   function GetEnumerator: IEnumerator;
 end;

</delphi>

Интерфейс IEnumerator определён следующим образом: <delphi>

 IEnumerator = interface(IInterface)
   function GetCurrent: TObject;
   function MoveNext: Boolean;
   procedure Reset;
   property Current: TObject read GetCurrent;
 end;

</delphi>

Расширения FPC

Приведенные ниже примеры не поддерживаются Delphi, и предназначены только для FPC.

Перечисления и диапазоны

В Delphi невозможно использовать в цикле типы перечислений и диапазонов:

<delphi> 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. </delphi>

Объявление перечислителей

В Delphi также невозможно добавить перечислитель не изменяя класс и добавить перечислитель к следующим типам не-классы/объекты/записи/интерфейсы. В FPC добавление перечислитиля для любого типа производится новым оператором operator Enumerator. Смотрите следующий пример:

<delphi> type

 TMyRecord = record F1: Integer; F2: array of TMyType; end;
 TMyArrayEnumerator = class
   constructor Create(const A: TMyRecord);
   function Current: TMyType;
   function MoveNext: Boolean;
 end;
 // Это новый встроенный оператор
 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. </delphi>

Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:

<delphi> type

 TUTF8StringEnumerator = class
 private
   FByteIndex: Integer;
   FCharIndex: Integer;
 public
   constructor Create(const A: UTF8String);
   function Current: UTF8Char;
   function MoveNext: Boolean;
 end;
 operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
 begin
   Result := TUTF8String.Create(A);
 end;

var

 s: UTF8String;
 ch: UTF8Char;
 i: Integer;

begin

 // Здесь требуется выполнить O(N^2) операций
 for i := 1 to Length(s) do
   DoSomething(ch[i]);
 // А здесь, только O(N) операций
 for ch in s do
   DoSomething(ch);

end. </delphi>

Использование имён идентификаторов отличных от MoveNext и Current

В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:

<delphi> 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. </delphi>

Proposed extensions

Select which enumerator to use

It is impossible to choose among different possible enumerators. For example you can traverse a tree using different orders. The well known algorithms are: preorder, postorder, inorder and breadth‑first traversals. Therefore it would be useful to have an ability to choose an enumerator. For example using the following syntax:

<delphi> type

 TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)

procedure TraverseTree(Tree: TTree); var

 Node: TNode;

begin

 // Variant1. For the class instances we can call the method Tree.GetEnumerator(teInOrder). 
 // For the classes we can call a class method
 for Node in Tree using GetEnumerator(teInOrder) do
   Dosomething(Node);
 // Variant2. Or we can call the global function
 for Node in Tree using GetEnumerator(Tree, teInOrder) do
   Dosomething(Node);
 // Variant3. In the previous variant 'in Tree' is useless so the next code is a simplified form:
 for Node using GetEnumerator(Tree, teInOrder) do
   Dosomething(Node);
 // Variant4. We can try to avoid new context key-word 'using' by calling method:
 for Node in Tree.GetSomeEnumerator(teInOrder) do
   Dosomething(Node);
 // but this brings ambiguity to the compiler since Tree.GetSomeEnumerator(teInOrder) can be translated into
 // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
 // This ambiguity might be resolvable by checking whether the class implements IEnumerator interface

end;

// for basic type we will call only the apropriate function procedure TraverseString(S: String); var

 C: Char;

begin

 for C in S using GetReverseStringEnumerator(S) do
   DoSomething(C);

end; </delphi>

Get enumerator Position if available

Finally, it is impossible to extract any information from the iterator except the current item. Sometimes other data, such as current index, may be useful:

<delphi> 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: UF8String): TUF8StringEnumerator;
 begin
   Result := TUF8String.Create(A);
 end;

var

 s: UF8String;
 ch: UF8Char;
 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. </delphi>

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.


Ссылки