Difference between revisions of "for-in loop/ru"
m (Text replace - "delphi>" to "syntaxhighlight>") |
|||
Line 9: | Line 9: | ||
=== Цикл применимый к строкам === | === Цикл применимый к строкам === | ||
− | < | + | <syntaxhighlight> |
procedure StringLoop(S: String); | procedure StringLoop(S: String); | ||
var | var | ||
Line 17: | Line 17: | ||
DoSomething(C); | DoSomething(C); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
=== Цикл применимый к массиву === | === Цикл применимый к массиву === | ||
− | < | + | <syntaxhighlight> |
procedure ArrayLoop(A: Array of Byte); | procedure ArrayLoop(A: Array of Byte); | ||
var | var | ||
Line 29: | Line 29: | ||
DoSomething(B); | DoSomething(B); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
=== Цикл применимый к множеству === | === Цикл применимый к множеству === | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TColor = (cRed, cGren, cBlue); | TColor = (cRed, cGren, cBlue); | ||
Line 44: | Line 44: | ||
DoSomething(Color); | DoSomething(Color); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
=== Применение к классам контейнерам === | === Применение к классам контейнерам === | ||
Line 50: | Line 50: | ||
Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне: | Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне: | ||
− | < | + | <syntaxhighlight> |
TSomeEnumerator = class | TSomeEnumerator = class | ||
public | public | ||
Line 56: | Line 56: | ||
property Current: TSomeType; | property Current: TSomeType; | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
Для реализации перечислителя необходимы: метод <b>MoveNext</b>, который увеличивает позицию элемента перечисления и свойство <b>Current</b>, в котором возвращается выбранный вами тип данных. | Для реализации перечислителя необходимы: метод <b>MoveNext</b>, который увеличивает позицию элемента перечисления и свойство <b>Current</b>, в котором возвращается выбранный вами тип данных. | ||
Line 63: | Line 63: | ||
Пример: | Пример: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TEnumerableTree = class; | TEnumerableTree = class; | ||
Line 104: | Line 104: | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
После этого вы можете использовать следующий код: | После этого вы можете использовать следующий код: | ||
− | < | + | <syntaxhighlight> |
procedure TreeLoop(ATree: TEnumerableTree); | procedure TreeLoop(ATree: TEnumerableTree); | ||
var | var | ||
Line 116: | Line 116: | ||
DoSomething(ANode); | DoSomething(ANode); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ... | Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ... | ||
Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс: | Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс: | ||
− | < | + | <syntaxhighlight> |
IEnumerable = interface(IInterface) | IEnumerable = interface(IInterface) | ||
function GetEnumerator: IEnumerator; | function GetEnumerator: IEnumerator; | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
Интерфейс <b>IEnumerator</b> определён следующим образом: | Интерфейс <b>IEnumerator</b> определён следующим образом: | ||
− | < | + | <syntaxhighlight> |
IEnumerator = interface(IInterface) | IEnumerator = interface(IInterface) | ||
function GetCurrent: TObject; | function GetCurrent: TObject; | ||
Line 135: | Line 135: | ||
property Current: TObject read GetCurrent; | property Current: TObject read GetCurrent; | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
== Расширения FPC == | == Расширения FPC == | ||
Line 144: | Line 144: | ||
В Delphi невозможно использовать в цикле типы перечислений и диапазонов: | В Delphi невозможно использовать в цикле типы перечислений и диапазонов: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TColor = (clRed, clBlue, clBlack); | TColor = (clRed, clBlue, clBlack); | ||
Line 157: | Line 157: | ||
DoSomethingOther(ch); | DoSomethingOther(ch); | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
=== Объявление перечислителей === | === Объявление перечислителей === | ||
Line 164: | Line 164: | ||
В FPC добавление перечислитиля для любого типа производится новым оператором <b>operator Enumerator</b>. Смотрите следующий пример: | В FPC добавление перечислитиля для любого типа производится новым оператором <b>operator Enumerator</b>. Смотрите следующий пример: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TMyRecord = record F1: Integer; F2: array of TMyType; end; | TMyRecord = record F1: Integer; F2: array of TMyType; end; | ||
Line 186: | Line 186: | ||
DoSomething(V); | DoSomething(V); | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8: | Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TUTF8StringEnumerator = class | TUTF8StringEnumerator = class | ||
Line 219: | Line 219: | ||
DoSomething(ch); | DoSomething(ch); | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
=== Использование имён идентификаторов отличных от MoveNext и Current === | === Использование имён идентификаторов отличных от MoveNext и Current === | ||
В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример: | В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
{ TMyListEnumerator } | { TMyListEnumerator } | ||
Line 273: | Line 273: | ||
List.Free; | List.Free; | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
== Proposed extensions == | == Proposed extensions == | ||
Line 279: | Line 279: | ||
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: | 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: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst) | TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst) | ||
Line 315: | Line 315: | ||
DoSomething(C); | DoSomething(C); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
=== Get enumerator Position if available === | === Get enumerator Position if available === | ||
Line 321: | Line 321: | ||
except the current item. Sometimes other data, such as current index, may be useful: | except the current item. Sometimes other data, such as current index, may be useful: | ||
− | < | + | <syntaxhighlight> |
type | type | ||
TUTF8StringEnumerator = class | TUTF8StringEnumerator = class | ||
Line 360: | Line 360: | ||
Writeln(i, ': ', ch); | Writeln(i, ': ', ch); | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
Note that index might return arbitrary type, not necessarily integer. | Note that index might return arbitrary type, not necessarily integer. |
Revision as of 11:27, 24 March 2012
│
English (en) │
français (fr) │
日本語 (ja) │
русский (ru) │
"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция доступна сейчас с версии fpc 2.4.2.
Delphi и FPC реализация
Она имеет следующий синтаксис:
Цикл применимый к строкам
procedure StringLoop(S: String);
var
C: Char;
begin
for C in S do
DoSomething(C);
end;
Цикл применимый к массиву
procedure ArrayLoop(A: Array of Byte);
var
B: Byte;
begin
for B in A do
DoSomething(B);
end;
Цикл применимый к множеству
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;
Применение к классам контейнерам
Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:
TSomeEnumerator = class
public
function MoveNext: Boolean;
property Current: TSomeType;
end;
Для реализации перечислителя необходимы: метод MoveNext, который увеличивает позицию элемента перечисления и свойство Current, в котором возвращается выбранный вами тип данных.
Следующим шагом необходимо добавить к классу контейнеру специального метода GetEnumerator, который будет возвращать экземпляр перечислителя.
Пример:
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;
После этого вы можете использовать следующий код:
procedure TreeLoop(ATree: TEnumerableTree);
var
ANode: TNode;
begin
for ANode in ATree do
DoSomething(ANode);
end;
Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...
Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс:
IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;
Интерфейс IEnumerator определён следующим образом:
IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;
Расширения FPC
Приведенные ниже примеры не поддерживаются Delphi, и предназначены только для FPC.
Перечисления и диапазоны
В 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 также невозможно добавить перечислитель не изменяя класс и добавить перечислитель к следующим типам не-классы/объекты/записи/интерфейсы. В FPC добавление перечислитиля для любого типа производится новым оператором operator Enumerator. Смотрите следующий пример:
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.
Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:
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.
Использование имён идентификаторов отличных от MoveNext и Current
В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:
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
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:
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;
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:
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.
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.