Sign in Join
Wiki copytemporary

for-in loop/ru: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: for-in loop/ru

OldNewLine
1+"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция доступна сейчас с версии fpc 2.4.2.
2+
3+## Delphi и FPC реализация
4+
5+Она имеет следующий синтаксис:
6+
7+### Цикл применимый к строкам
8+
9+``` pascal
10+procedure StringLoop(S: String);
11+var
12+ C: Char;
13+begin
14+ for C in S do
15+ DoSomething(C);
16+end;
17+```
18+
19+### Цикл применимый к массиву
20+
21+``` pascal
22+procedure ArrayLoop(A: Array of Byte);
23+var
24+ B: Byte;
25+begin
26+ for B in A do
27+ DoSomething(B);
28+end;
29+```
30+
31+### Цикл применимый к множеству
32+
33+``` pascal
34+type
35+ TColor = (cRed, cGren, cBlue);
36+ TColors = set of TColor;
37+procedure SetLoop(Colors: TColors);
38+var
39+ Color: TColor;
40+begin
41+ for Color in Colors do
42+ DoSomething(Color);
43+end;
44+```
45+
46+### Применение к классам контейнерам
47+
48+Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:
49+
50+``` pascal
51+TSomeEnumerator = class
52+public
53+ function MoveNext: Boolean;
54+ property Current: TSomeType;
55+end;
56+```
57+
58+Для реализации перечислителя необходимы: метод MoveNext, который увеличивает позицию элемента перечисления и свойство Current, в котором возвращается выбранный вами тип данных.
59+
60+Следующим шагом необходимо добавить к классу контейнеру специального метода GetEnumerator, который будет возвращать экземпляр перечислителя.
61+
62+Пример:
63+
64+``` pascal
65+type
66+ TEnumerableTree = class;
67+
68+ TTreeEnumerator = class
69+ private
70+ FTree: TEnumerableTree;
71+ FCurrent: TNode;
72+ public
73+ constructor Create(ATree: TEnumerableTree);
74+ function MoveNext: Boolean;
75+ property Current: TNode read FCurrent;
76+ end;
77+
78+ TEnumerableTree = class
79+ public
80+ function GetEnumerator: TTreeEnumerator;
81+ end;
82+
83+constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
84+begin
85+ inherited Create;
86+ FTree := ATree;
87+ FCurrent := nil;
88+end;
89+
90+function TTreeEnumerator.MoveNext: Boolean;
91+begin
92+ // получение следующего узла дерева
93+ if FCurrent = nil then
94+ FCurrent := FTree.GetFirstNode
95+ else
96+ FCurrent := FTree.GetNextNode(FCurrent);
97+ Result := FCurrent <> FTree.GetLastNode;
98+end;
99+
100+function TEnumerableTree.GetEnumerator: TTreeEnumerator;
101+begin
102+ Result := TTreeEnumerator.Create(Self);
103+end;
104+```
105+
106+После этого вы можете использовать следующий код:
107+
108+``` pascal
109+procedure TreeLoop(ATree: TEnumerableTree);
110+var
111+ ANode: TNode;
112+begin
113+ for ANode in ATree do
114+ DoSomething(ANode);
115+end;
116+```
117+
118+Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...
119+
120+Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс:
121+
122+``` pascal
123+ IEnumerable = interface(IInterface)
124+ function GetEnumerator: IEnumerator;
125+ end;
126+```
127+
128+Интерфейс IEnumerator определён следующим образом:
129+
130+``` pascal
131+ IEnumerator = interface(IInterface)
132+ function GetCurrent: TObject;
133+ function MoveNext: Boolean;
134+ procedure Reset;
135+ property Current: TObject read GetCurrent;
136+ end;
137+```
138+
139+## Расширения FPC
140+
141+Приведенные ниже примеры не поддерживаются Delphi, и предназначены только для FPC.
142+
143+### Перечисления и диапазоны
144+
145+В Delphi невозможно использовать в цикле типы перечислений и диапазонов:
146+
147+``` pascal
148+type
149+ TColor = (clRed, clBlue, clBlack);
150+ TRange = 'a'..'z';
151+var
152+ Color: TColor;
153+ ch: Char;
154+begin
155+ for Color in TColor do
156+ DoSomething(Color);
157+ for ch in TRange do
158+ DoSomethingOther(ch);
159+end.
160+```
161+
162+### Объявление перечислителей
163+
164+В Delphi также невозможно добавить перечислитель не изменяя класс и добавить перечислитель к следующим типам не-классы/объекты/записи/интерфейсы. В FPC добавление перечислитиля для любого типа производится новым оператором operator Enumerator. Смотрите следующий пример:
165+
166+``` pascal
167+type
168+ TMyRecord = record F1: Integer; F2: array of TMyType; end;
169+ TMyArrayEnumerator = class
170+ constructor Create(const A: TMyRecord);
171+ function Current: TMyType;
172+ function MoveNext: Boolean;
173+ end;
174+
175+ // Это новый встроенный оператор
176+ operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
177+ begin
178+ Result := TMyArrayEnumerator.Create(A);
179+ end;
180+
181+var
182+ A: MyRecord;
183+ V: TMyType
184+begin
185+ for V in A do
186+ DoSomething(V);
187+end.
188+```
189+
190+Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:
191+
192+``` pascal
193+type
194+ TUTF8StringEnumerator = class
195+ private
196+ FByteIndex: Integer;
197+ FCharIndex: Integer;
198+ public
199+ constructor Create(const A: UTF8String);
200+ function Current: UTF8Char;
201+ function MoveNext: Boolean;
202+ end;
203+
204+ operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
205+ begin
206+ Result := TUTF8String.Create(A);
207+ end;
208+
209+var
210+ s: UTF8String;
211+ ch: UTF8Char;
212+ i: Integer;
213+begin
214+ // Здесь требуется выполнить O(N^2) операций
215+ for i := 1 to Length(s) do
216+ DoSomething(ch[i]);
217+ // А здесь, только O(N) операций
218+ for ch in s do
219+ DoSomething(ch);
220+end.
221+```
222+
223+### Использование имён идентификаторов отличных от MoveNext и Current
224+
225+В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:
226+
227+``` pascal
228+type
229+ { TMyListEnumerator }
230+
231+ TMyListEnumerator = object
232+ private
233+ FCurrent: Integer;
234+ public
235+ constructor Create;
236+ destructor Destroy;
237+ function StepNext: Boolean; enumerator MoveNext;
238+ property Value: Integer read FCurrent; enumerator Current;
239+ end;
240+
241+ TMyList = class
242+ end;
243+
244+{ TMyListEnumerator }
245+
246+constructor TMyListEnumerator.Create;
247+begin
248+ FCurrent := 0;
249+end;
250+
251+destructor TMyListEnumerator.Destroy;
252+begin
253+ inherited;
254+end;
255+
256+function TMyListEnumerator.StepNext: Boolean;
257+begin
258+ inc(FCurrent);
259+ Result := FCurrent <= 3;
260+end;
261+
262+operator enumerator (AList: TMyList): TMyListEnumerator;
263+begin
264+ Result.Create;
265+end;
266+
267+var
268+ List: TMyList;
269+ i: integer;
270+begin
271+ List := TMyList.Create;
272+ for i in List do
273+ WriteLn(i);
274+ List.Free;
275+end.
276+```
277+
278+## Предлагаемые расширения
279+
280+### Выбор перечисления для использования
281+
282+Возможно использовать различные перечисления. Например, вы можете обойти дерево, используя различный порядок. Хорошо известны алгоритмы: предварительный порядок, обратный порядок, симметричный порядок и обход в ширину. Поэтому, полезно иметь возможность выбора перечисления. Например, используя следующий синтаксис:
283+
284+``` pascal
285+type
286+ TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
287+procedure TraverseTree(Tree: TTree);
288+var
289+ Node: TNode;
290+begin
291+ // Вариант 1. Для экземляров класса мы можем вызвать метод Tree.GetEnumerator(teInOrder).
292+ // Для классов мы можем вызвать метод класса
293+ for Node in Tree using GetEnumerator(teInOrder) do
294+ Dosomething(Node);
295+
296+ // Вариант 2. Или мы можем вызвать глобальную функцию
297+ for Node in Tree using GetEnumerator(Tree, teInOrder) do
298+ Dosomething(Node);
299+
300+ // Вариант 3. В предыдущем варианте 'in Tree' бесполезно, поэтому следующий код - это упрощенная форма:
301+ for Node using GetEnumerator(Tree, teInOrder) do
302+ Dosomething(Node);
303+
304+ // Вариант 4. Мы можем попытаться избежать нового контекстного ключевого слова 'using', вызывая метод:
305+ for Node in Tree.GetSomeEnumerator(teInOrder) do
306+ Dosomething(Node);
307+ // но это принесет двусмысленность для компилятора, т.к. Tree.GetSomeEnumerator(teInOrder) может быть транслирован в
308+ // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
309+ // Эта двусмысленность может быть разрешена проверкой, где класс применяет интерфейс IEnumerator
310+end;
311+
312+// для базового типа мы вызовем только подходящую функцию
313+procedure TraverseString(S: String);
314+var
315+ C: Char;
316+begin
317+ for C in S using GetReverseStringEnumerator(S) do
318+ DoSomething(C);
319+end;
320+```
321+
322+### Получение Позиции перечисления, если возможно
323+
324+Наконец, возможно извлечь любую информацию из интератора, кроме текущего элемента. Иногда любые данные, такие как текущий индекс, могут быть полезны:
325+
326+``` pascal
327+type
328+ TUTF8StringEnumerator = class
329+ private
330+ FByteIndex: Integer;
331+ FCharIndex: Integer;
332+ public
333+ constructor Create(const A: UTF8String);
334+ function Current: UTF8Char;
335+ function CurrentIndex: Integer;
336+ function MoveNext: Boolean;
337+ end;
338+
339+ operator GetEnumerator(A: UF8String): TUF8StringEnumerator;
340+ begin
341+ Result := TUF8String.Create(A);
342+ end;
343+
344+var
345+ s: UF8String;
346+ ch: UF8Char;
347+ i: Integer;
348+begin
349+
350+ // Неэффективный метод, как написано выше
351+ for i := 1 to Length(s) do
352+ Writeln(i, ': ', ch[i]);
353+
354+ // Нормальный, но уродливый
355+ i := 1;
356+ for ch in s do begin
357+ Writeln(i, ': ', ch);
358+ Inc(i);
359+ end;
360+
361+ // Предлагаемое расширение
362+ for ch in s index i do
363+ Writeln(i, ': ', ch);
364+end.
365+```
366+
367+Запомните, что индекс может вернуть произвольный тип, не обязательно целый тип. Например,в случае обхода дерева индекс может вернуть массив узлов на пути от корня к текущему узлу.
368+
369+## Ссылки
370+
371+- <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
372+
373+---
374+*From the Free Pascal wiki, page "for-in loop/ru", as it stood on 2020-02-15 after a change by Trev, 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).*

Back to history