Difference between revisions of "for-in loop/ru"

From Lazarus wiki
Jump to navigationJump to search
m
m (Fixed syntax highlighting; deleted category included in page template)
 
(6 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
{{for-in loop}}
 
{{for-in loop}}
  
"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция реализована сейчас в основной ветви разработки fpc.
+
"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция доступна сейчас с версии fpc 2.4.2.
  
 
== Delphi и FPC реализация ==
 
== Delphi и FPC реализация ==
Line 9: Line 9:
 
=== Цикл применимый к строкам ===
 
=== Цикл применимый к строкам ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure StringLoop(S: String);
 
procedure StringLoop(S: String);
 
var
 
var
Line 17: Line 17:
 
     DoSomething(C);
 
     DoSomething(C);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Цикл применимый к массиву ===
 
=== Цикл применимый к массиву ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure ArrayLoop(A: Array of Byte);
 
procedure ArrayLoop(A: Array of Byte);
 
var
 
var
Line 29: Line 29:
 
     DoSomething(B);
 
     DoSomething(B);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Цикл применимый к множеству  ===
 
=== Цикл применимый к множеству  ===
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TColor = (cRed, cGren, cBlue);
 
   TColor = (cRed, cGren, cBlue);
Line 44: Line 44:
 
     DoSomething(Color);
 
     DoSomething(Color);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Применение к классам контейнерам ===
 
=== Применение к классам контейнерам ===
Line 50: Line 50:
 
Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:
 
Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
TSomeEnumerator = class
 
TSomeEnumerator = class
 
public
 
public
Line 56: Line 56:
 
   property Current: TSomeType;
 
   property Current: TSomeType;
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
Для реализации перечислителя необходимы: метод <b>MoveNext</b>, который увеличивает позицию элемента перечисления и свойство <b>Current</b>, в котором возвращается выбранный вами тип данных.
 
Для реализации перечислителя необходимы: метод <b>MoveNext</b>, который увеличивает позицию элемента перечисления и свойство <b>Current</b>, в котором возвращается выбранный вами тип данных.
Line 63: Line 63:
  
 
Пример:
 
Пример:
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TEnumerableTree = class;
 
   TEnumerableTree = class;
Line 104: Line 104:
 
end;
 
end;
  
</delphi>
+
</syntaxhighlight>
  
 
После этого вы можете использовать следующий код:
 
После этого вы можете использовать следующий код:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure TreeLoop(ATree: TEnumerableTree);
 
procedure TreeLoop(ATree: TEnumerableTree);
 
var
 
var
Line 116: Line 116:
 
     DoSomething(ANode);
 
     DoSomething(ANode);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...
 
Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...
  
 
Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс:
 
Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс:
<delphi>
+
<syntaxhighlight lang=pascal>
 
   IEnumerable = interface(IInterface)
 
   IEnumerable = interface(IInterface)
 
     function GetEnumerator: IEnumerator;
 
     function GetEnumerator: IEnumerator;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
  
 
Интерфейс <b>IEnumerator</b> определён следующим образом:
 
Интерфейс <b>IEnumerator</b> определён следующим образом:
<delphi>
+
<syntaxhighlight lang=pascal>
 
   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;
</delphi>
+
</syntaxhighlight>
  
 
== Расширения FPC ==
 
== Расширения FPC ==
Line 144: Line 144:
 
В Delphi невозможно использовать в цикле типы перечислений и диапазонов:
 
В Delphi невозможно использовать в цикле типы перечислений и диапазонов:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TColor = (clRed, clBlue, clBlack);
 
   TColor = (clRed, clBlue, clBlack);
Line 157: Line 157:
 
     DoSomethingOther(ch);
 
     DoSomethingOther(ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
=== Объявление перечислителей ===
 
=== Объявление перечислителей ===
Line 164: Line 164:
 
В FPC добавление перечислитиля для любого типа производится новым оператором <b>operator Enumerator</b>. Смотрите следующий пример:
 
В FPC добавление перечислитиля для любого типа производится новым оператором <b>operator Enumerator</b>. Смотрите следующий пример:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
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.
</delphi>
+
</syntaxhighlight>
  
 
Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:
 
Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TUTF8StringEnumerator = class
 
   TUTF8StringEnumerator = class
Line 202: Line 202:
 
   end;
 
   end;
  
   operator Enumerator(A: UF8String): TUF8StringEnumerator;
+
   operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
 
   begin
 
   begin
     Result := TUF8String.Create(A);
+
     Result := TUTF8String.Create(A);
 
   end;
 
   end;
  
 
var
 
var
   s: UF8String;
+
   s: UTF8String;
   ch: UF8Char;
+
   ch: UTF8Char;
 
   i: Integer;
 
   i: Integer;
 
begin
 
begin
Line 219: Line 219:
 
     DoSomething(ch);
 
     DoSomething(ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
=== Использование имён идентификаторов отличных от MoveNext и Current ===
 
=== Использование имён идентификаторов отличных от MoveNext и Current ===
 
В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:
 
В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   { TMyListEnumerator }
 
   { TMyListEnumerator }
Line 273: Line 273:
 
   List.Free;
 
   List.Free;
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
== 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>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
 
   TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
Line 286: Line 286:
 
   Node: TNode;
 
   Node: TNode;
 
begin
 
begin
   // Variant1. For the class instances we can call the method Tree.GetEnumerator(teInOrder).  
+
   // Вариант 1. Для экземляров класса мы можем вызвать метод Tree.GetEnumerator(teInOrder).  
   // For the classes we can call a class method
+
   // Для классов мы можем вызвать метод класса
 
   for Node in Tree using GetEnumerator(teInOrder) do
 
   for Node in Tree using GetEnumerator(teInOrder) do
 
     Dosomething(Node);
 
     Dosomething(Node);
  
   // Variant2. Or we can call the global function
+
   // Вариант 2. Или мы можем вызвать глобальную функцию
 
   for Node in Tree using GetEnumerator(Tree, teInOrder) do
 
   for Node in Tree using GetEnumerator(Tree, teInOrder) do
 
     Dosomething(Node);
 
     Dosomething(Node);
  
   // Variant3. In the previous variant 'in Tree' is useless so the next code is a simplified form:
+
   // Вариант 3. В предыдущем варианте 'in Tree' бесполезно, поэтому следующий код - это упрощенная форма:
 
   for Node using GetEnumerator(Tree, teInOrder) do
 
   for Node using GetEnumerator(Tree, teInOrder) do
 
     Dosomething(Node);
 
     Dosomething(Node);
  
   // Variant4. We can try to avoid new context key-word 'using' by calling method:
+
   // Вариант 4. Мы можем попытаться избежать нового контекстного ключевого слова 'using', вызывая метод:
 
   for Node in Tree.GetSomeEnumerator(teInOrder) do
 
   for Node in Tree.GetSomeEnumerator(teInOrder) do
 
     Dosomething(Node);
 
     Dosomething(Node);
   // but this brings ambiguity to the compiler since Tree.GetSomeEnumerator(teInOrder) can be translated into
+
   // но это принесет двусмысленность для компилятора, т.к. Tree.GetSomeEnumerator(teInOrder) может быть транслирован в
 
   // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
 
   // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
   // This ambiguity might be resolvable by checking whether the class implements IEnumerator interface
+
   // Эта двусмысленность может быть разрешена проверкой, где класс применяет интерфейс IEnumerator
 
end;
 
end;
  
// for basic type we will call only the apropriate function
+
// для базового типа мы вызовем только подходящую функцию
 
procedure TraverseString(S: String);
 
procedure TraverseString(S: String);
 
var
 
var
Line 315: Line 315:
 
     DoSomething(C);
 
     DoSomething(C);
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
=== 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>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TUTF8StringEnumerator = class
 
   TUTF8StringEnumerator = class
Line 345: Line 344:
 
begin
 
begin
  
   // Inefficient, as discussed above
+
   // Неэффективный метод, как написано выше
 
   for i := 1 to Length(s) do
 
   for i := 1 to Length(s) do
 
     Writeln(i, ': ', ch[i]);
 
     Writeln(i, ': ', ch[i]);
  
   // Ok, but ugly
+
   // Нормальный, но уродливый
 
   i := 1;
 
   i := 1;
 
   for ch in s do begin
 
   for ch in s do begin
Line 356: Line 355:
 
   end;
 
   end;
  
   // Proposed extension
+
   // Предлагаемое расширение
 
   for ch in s index i do
 
   for ch in s index i do
 
     Writeln(i, ': ', ch);
 
     Writeln(i, ': ', ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
 
 
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.
 
 
 
  
 +
Запомните, что индекс может вернуть произвольный тип, не обязательно целый тип. Например,в случае обхода дерева индекс может вернуть массив узлов на пути от корня к текущему узлу.
  
 
== Ссылки ==
 
== Ссылки ==
 
* http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html
 
* http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html

Latest revision as of 01:21, 16 February 2020

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.

Предлагаемые расширения

Выбор перечисления для использования

Возможно использовать различные перечисления. Например, вы можете обойти дерево, используя различный порядок. Хорошо известны алгоритмы: предварительный порядок, обратный порядок, симметричный порядок и обход в ширину. Поэтому, полезно иметь возможность выбора перечисления. Например, используя следующий синтаксис:

type
  TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
procedure TraverseTree(Tree: TTree);
var
  Node: TNode;
begin
  // Вариант 1. Для экземляров класса мы можем вызвать метод Tree.GetEnumerator(teInOrder). 
  // Для классов мы можем вызвать метод класса
  for Node in Tree using GetEnumerator(teInOrder) do
    Dosomething(Node);

  // Вариант 2. Или мы можем вызвать глобальную функцию
  for Node in Tree using GetEnumerator(Tree, teInOrder) do
    Dosomething(Node);

  // Вариант 3. В предыдущем варианте 'in Tree' бесполезно, поэтому следующий код - это упрощенная форма:
  for Node using GetEnumerator(Tree, teInOrder) do
    Dosomething(Node);

  // Вариант 4. Мы можем попытаться избежать нового контекстного ключевого слова 'using', вызывая метод:
  for Node in Tree.GetSomeEnumerator(teInOrder) do
    Dosomething(Node);
  // но это принесет двусмысленность для компилятора, т.к. Tree.GetSomeEnumerator(teInOrder) может быть транслирован в
  // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
  // Эта двусмысленность может быть разрешена проверкой, где класс применяет интерфейс IEnumerator
end;

// для базового типа мы вызовем только подходящую функцию
procedure TraverseString(S: String);
var
  C: Char;
begin
  for C in S using GetReverseStringEnumerator(S) do
    DoSomething(C);
end;

Получение Позиции перечисления, если возможно

Наконец, возможно извлечь любую информацию из интератора, кроме текущего элемента. Иногда любые данные, такие как текущий индекс, могут быть полезны:

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

  // Неэффективный метод, как написано выше
  for i := 1 to Length(s) do
    Writeln(i, ': ', ch[i]);

  // Нормальный, но уродливый
  i := 1;
  for ch in s do begin
    Writeln(i, ': ', ch);
    Inc(i);
  end;

  // Предлагаемое расширение
  for ch in s index i do
    Writeln(i, ': ', ch);
end.

Запомните, что индекс может вернуть произвольный тип, не обязательно целый тип. Например,в случае обхода дерева индекс может вернуть массив узлов на пути от корня к текущему узлу.

Ссылки