Sign in Join
Wiki copytemporary

for-in loop/ja: what changed

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

Title: for-in loop/ja

OldNewLine
1+これは集合に対して繰り返しを行うが、[[For|基本的な forループは]]数値によるインデクス・カウンタを用いるのに対して、for-inループは代わりに、直接使用されるカウンタ変数に集合要素を代入する。For-inは繰り返しが必要とされる文字列、配列、セットおよび他の任意の集合で有効である。空の集合に対するループは何もしない。カウンタ変数はループの中で変更できない。
2+
3+**For-in loop**構文はDelphi 2005以降で導入された。FPC2.4.2で実装された。
4+
5+公式なドキュメントはここである: [Reference guide chapter 13](https://www.freepascal.org/docs-html/ref/refsu58.html)
6+
7+## DelphiとFPCでの実装
8+
9+**For in**ループの文法は以下である:
10+
11+### 文字列ループ
12+
13+``` pascal
14+procedure StringLoop(S: String);
15+var
16+ C: Char;
17+begin
18+ for C in S do
19+ DoSomething(C);
20+end;
21+```
22+
23+### 配列ループ
24+
25+``` pascal
26+procedure ArrayLoop(A: Array of Byte);
27+var
28+ B: Byte;
29+begin
30+ for B in A do
31+ DoSomething(B);
32+end;
33+```
34+
35+### セットループ
36+
37+``` pascal
38+type
39+ TColor = (cRed, cGren, cBlue);
40+ TColors = set of TColor;
41+procedure SetLoop(Colors: TColors);
42+var
43+ Color: TColor;
44+begin
45+ for Color in Colors do
46+ DoSomething(Color);
47+end;
48+```
49+
50+### ループ変数はコンテナ値の一時的コピーである
51+
52+ループ変数は [for-inループが実行されるコンテナ内に保存されている値のコピーである](https://stackoverflow.com/questions/18575736/delphi-dynamic-array-iteration-and-record-copyingsimple)。
53+
54+``` pascal
55+program tempcopy;
56+
57+{$IFDEF FPC}
58+ {$mode Delphi}
59+{$ENDIF}
60+
61+uses SysUtils;
62+
63+type
64+ PointerAddress = NativeUInt;
65+
66+var
67+ pIntArr: Pointer;
68+ IntArr: Array of Integer;
69+
70+procedure ArrayLoop(const AArr: Array of Integer);
71+var
72+ i: Integer;
73+begin
74+ pIntArr := @AArr;
75+ writeln('Argument: ', PointerAddress(pIntArr)); // <-- 最初の要素と同じメモリアドレス
76+ pIntArr := @i;
77+ writeln('Local loop variable: ', PointerAddress(pIntArr)); // <-- ローカルな一時変数 i
78+ for i in AArr do
79+ begin
80+ pIntArr := @i;
81+ writeln('Loop variable: ', PointerAddress(pIntArr)); // <-- ローカルな一時変数 i
82+ end;
83+end;
84+
85+begin
86+ // 3つの要素を配列に格納
87+ SetLength(IntArr, 3);
88+
89+ pIntArr := @pIntArr;
90+ writeln('global pIntArr address: ', PointerAddress(pIntArr));
91+
92+ // IntArrは最初の要素に対するポインタとは独立している
93+ pIntArr := @IntArr;
94+ writeln('global IntArr address: ', PointerAddress(pIntArr));
95+ pIntArr := @IntArr;
96+ writeln('IntArr points to: ', PointerAddress(pIntArr^));
97+
98+ // 1つの要素に対するアドレス
99+ pIntArr := @IntArr[0];
100+ writeln('Item 1: ', PointerAddress(pIntArr)); // <-- 最初の要素のメモリアドレス
101+ pIntArr := @IntArr[1];
102+ writeln('Item 2: ', PointerAddress(pIntArr));
103+ pIntArr := @IntArr[2];
104+ writeln('Item 3: ', PointerAddress(pIntArr));
105+
106+ writeln('array loop');
107+ ArrayLoop(IntArr);
108+end.
109+```
110+
111+### コンテナの走査
112+
113+コンテナクラスを走査するには**加算子(enumerator)**が必要である。**加算子**は以下のテンプレートによればクラス構造である:
114+
115+``` pascal
116+TSomeEnumerator = class
117+public
118+ function MoveNext: Boolean;
119+ property Current: TSomeType;
120+end;
121+```
122+
123+加算子クラスに必要なのは以下の2つ: 加算子に次を読み込ませるMoveNextメソッドと有効な型を返す**Current**プロパティのみである。 その後は加算子インスタンスを返すコンテナクラスに不思議なGetEnumeratorメソッドを追加する必要がある。 例えば:
124+
125+``` pascal
126+type
127+ TEnumerableTree = class;
128+
129+ TTreeEnumerator = class
130+ private
131+ FTree: TEnumerableTree;
132+ FCurrent: TNode;
133+ public
134+ constructor Create(ATree: TEnumerableTree);
135+ function MoveNext: Boolean;
136+ property Current: TNode read FCurrent;
137+ end;
138+
139+ TEnumerableTree = class
140+ public
141+ function GetEnumerator: TTreeEnumerator;
142+ end;
143+
144+constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
145+begin
146+ inherited Create;
147+ FTree := ATree;
148+ FCurrent := nil;
149+end;
150+
151+function TTreeEnumerator.MoveNext: Boolean;
152+begin
153+ // Treeから次のノードを取得するロジック
154+ if FCurrent = nil then
155+ FCurrent := FTree.GetFirstNode
156+ else
157+ FCurrent := FTree.GetNextNode(FCurrent);
158+ Result := FCurrent <> nil;
159+end;
160+
161+function TEnumerableTree.GetEnumerator: TTreeEnumerator;
162+begin
163+ Result := TTreeEnumerator.Create(Self);
164+ // 注意: このResultはループの後にコンパイラによって自動的に解放される。
165+end;
166+```
167+
168+この後、以下のコードが実行できる:
169+
170+``` pascal
171+procedure TreeLoop(ATree: TEnumerableTree);
172+var
173+ ANode: TNode;
174+begin
175+ for ANode in ATree do
176+ DoSomething(ANode);
177+end;
178+```
179+
180+いくつかの基底クラス(例えば TList, TStrings, TCollection, TComponent ...)に加算子がすでに組み込まれていることに気づくだろう。 また、もし加算可能なコンテナクラスに以下のインターフェイスを実装すると、どのクラスも加算可能となる:
181+
182+``` pascal
183+ IEnumerable = interface(IInterface)
184+ function GetEnumerator: IEnumerator;
185+ end;
186+```
187+
188+ここで IEnumerator以下として宣言されている:
189+
190+``` pascal
191+ IEnumerator = interface(IInterface)
192+ function GetCurrent: TObject;
193+ function MoveNext: Boolean;
194+ procedure Reset;
195+ property Current: TObject read GetCurrent;
196+ end;
197+```
198+
199+### 1つのクラスに対する複数の加算子
200+
201+クラス、オブジェクト、レコードに加算子を加えることができる。以下は逆順でTEnumerableTreeを走査する加算子の例である:
202+
203+``` pascal
204+type
205+ TEnumerableTree = class;
206+
207+ TTreeEnumerator = class
208+ ...正順で走査する、上記参照のこと...
209+ end;
210+
211+ TTreeReverseEnumerator = class
212+ private
213+ FTree: TEnumerableTree;
214+ FCurrent: TNode;
215+ public
216+ constructor Create(ATree: TEnumerableTree);
217+ function MoveNext: Boolean;
218+ property Current: TNode read FCurrent;
219+ function GetEnumerator: TTreeReverseEnumerator; // それ自身を返す
220+ end;
221+
222+ TEnumerableTree = class
223+ public
224+ function GetEnumerator: TTreeEnumerator;
225+ function GetReverseEnumerator: TTreeReverseEnumerator;
226+ end;
227+
228+...TTreeEnumeratorの実装部は上記参照のこと...
229+
230+constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
231+begin
232+ inherited Create;
233+ FTree := ATree;
234+end;
235+
236+function TTreeReverseEnumerator.MoveNext: Boolean;
237+begin
238+ // 次のノードをツリーから逆順で得るロジック
239+ if FCurrent = nil then
240+ FCurrent := FTree.GetLastNode
241+ else
242+ FCurrent := FTree.GetPrevNode(FCurrent);
243+ Result := FCurrent <> nil;
244+end;
245+
246+function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
247+begin
248+ Result := Self;
249+end;
250+
251+function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
252+begin
253+ Result := TTreeReverseEnumerator.Create(Self);
254+ // 注意: このResultはループの後にコンパイラによって自動的に解放される。
255+end;
256+```
257+
258+この後、以下のコードが実行できる:
259+
260+``` pascal
261+procedure TreeLoop(ATree: TEnumerableTree);
262+var
263+ ANode: TNode;
264+begin
265+ for ANode in ATree.GetReverseEnumerator do
266+ DoSomething(ANode);
267+end;
268+```
269+
270+## FPCでの拡張
271+
272+次のコード例はFPCでのみ実装されており、Delphiではサポートされていない構文である。
273+
274+### 加算子とサブレンジ型の走査
275+
276+Delphiでは、[加算された型および部分範囲型を走査することのどちらも不可能](http://qc.embarcadero.com/wc/qcmain.aspx?d=106212)であるが、Free Pascalでは以下のように書ける:
277+
278+``` pascal
279+type
280+ TColor = (clRed, clBlue, clBlack);
281+ TRange = 'a'..'z';
282+var
283+ Color: TColor;
284+ ch: Char;
285+begin
286+ for Color in TColor do
287+ DoSomething(Color);
288+ for ch in TRange do
289+ DoSomethingOther(ch);
290+end.
291+```
292+
293+これは、ハードキャストが必ずしも加算値の部分ではない値に用いられるため型システムが無効にされるという、バグレポート0029147から取った以下の例で、さらに示すことができる:
294+
295+``` pascal
296+type
297+ TSomeEnums = (One, Two, Three);
298+
299+resourcestring
300+ SOne = 'One ape';
301+ STwo = 'Two apes';
302+ SThree = 'Three apes';
303+
304+const
305+ SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
306+ SOne, STwo, SThree);
307+
308+var
309+ i: Integer;
310+ SE: TSomeEnums;
311+
312+begin
313+ for i := 0 to 4 do begin
314+ SE := TSomeEnums(i); // ハードキャスト。2以上になりうるが、型システムは今無効である。
315+ WriteLn(SSomeEnumStrings[SE]);
316+ end;
317+end.
318+```
319+
320+このプログラマは自身が最善を知っていると仮定して、コンパイラに i が加算型あり、コンパイラがさらにチェックを行わないだろうと命令している。
321+
322+もちろんこのプログラマは誤っており、コンパイラはそうしないだけの分別はある...
323+
324+**For in do**で加算型を走査するにより、ハードキャストは余計であり、コードは型について安全である:
325+
326+``` pascal
327+type
328+ TSomeEnums = (One, Two, Three);
329+
330+resourcestring
331+ SOne = 'One ape';
332+ STwo = 'Two apes';
333+ SThree = 'Three apes';
334+
335+const
336+ SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
337+ SOne, STwo, SThree);
338+
339+var
340+ SE: TSomeEnums;
341+begin
342+ for SE in TSomeEnums do
343+ WriteLn(SSomeEnumStrings[SE]);
344+end.
345+```
346+
347+### 加算子を宣言する
348+
349+また、Delphiでもクラスを変更することなしに加算子を加えること、非クラス/オブジェクト/レコード/インターフェースに加算子を加えることのいずれも不可能である。
350+
351+FPCは、以下の例のように新しい文法、**operator**型、**Enumerator**を用いることでこれを可能としている:
352+
353+``` pascal
354+type
355+ TMyRecord = record F1: Integer; F2: array of TMyType; end;
356+ TMyArrayEnumerator = class
357+ private
358+ function GetCurrent: TMyType;
359+ public
360+ constructor Create(const A: TMyRecord);
361+ property Current: TMyType read GetCurrent;
362+ function MoveNext: Boolean;
363+ end;
364+
365+ // これが新しい組込演算子である。
366+ operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
367+ begin
368+ Result := TMyArrayEnumerator.Create(A);
369+ end;
370+
371+var
372+ A: MyRecord;
373+ V: TMyType
374+begin
375+ for V in A do
376+ DoSomething(V);
377+end.
378+```
379+
380+#### UTF-8文字列を走査する
381+
382+特に有用な例として、上記の拡張はUTF-8文字列を走査するうえで非常に効率が良い:
383+
384+``` pascal
385+uses
386+ LazUTF8;
387+interface
388+type
389+ { TUTF8StringAsStringEnumerator
390+ UTF-8文字の正確なエンコーディングを知る、あるいはコードの中の今の文字列定数を
391+ 使いたいときに有用である。
392+ 安全性の観点より、代わりにコードポイント値(基数)を使うべきである。
393+ もし速度を重視するときは、加算子を用いてはいけない。代わりにMoveNextメソッドで
394+ 示されているようにPCharを直接用い、UTF-8を読むこと。これはいくつかの興味深い特
395+ 徴である。}
396+
397+ TUTF8StringAsStringEnumerator = class
398+ private
399+ fCurrent: UTF8String;
400+ fCurrentPos, fEndPos: PChar;
401+ function GetCurrent: UTF8String;
402+ public
403+ constructor Create(const A: UTF8String);
404+ property Current: UTF8String read GetCurrent;
405+ function MoveNext: Boolean;
406+ end;
407+
408+ operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
409+
410+var
411+ Form1: TForm1;
412+
413+implementation
414+
415+operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
416+begin
417+ Result := TUTF8StringAsStringEnumerator.Create(A);
418+end;
419+
420+{ TUTF8StringAsStringEnumerator }
421+
422+function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
423+begin
424+ Result:=fCurrent;
425+end;
426+
427+constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
428+begin
429+ fCurrentPos:=PChar(A); // 注意: もしA=''ならばPChar(A)は#0文字列に対するポインタを返す
430+ fEndPos:=fCurrentPos+length(A);
431+end;
432+
433+function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
434+var
435+ l: Integer;
436+begin
437+ if fCurrentPos<fEndPos then
438+ begin
439+ l:=UTF8CharacterLength(fCurrentPos);
440+ SetLength(fCurrent,l);
441+ Move(fCurrentPos^,fCurrent[1],l);
442+ inc(fCurrentPos,l);
443+ Result:=true;
444+ end else
445+ Result:=false;
446+end;
447+
448+{ TForm1 }
449+
450+procedure TForm1.FormCreate(Sender: TObject);
451+var
452+ s, ch: UTF8String;
453+ i: SizeInt;
454+begin
455+ s:='mäßig';
456+
457+ // このようにUTF8LengthとUTF8Copyを用いるのは遅い、O(n)^2を要する
458+ for i:=1 to UTF8Length(s) do
459+ writeln('ch=',UTF8Copy(s,i,1));
460+
461+ // 上記の加算子を用いるることは手短で極めて速い、O(n)を要する
462+ for ch in s do
463+ writeln('ch=',ch);
464+end;
465+```
466+
467+### 組込のMoveNextとCurrentの代わりにどのような定義子も用いる
468+
469+Delphiでは加算子の中でMoveNextという名とCurrentという名の関数を適切に用いなければならない。 FPCでは望む名前が何であれ用いることができる。これは文法、'enumerator MoveNext;'と'enumerator Current;'において**enumerator**修飾子の使用で可能となっている。以下の例にあるように:
470+
471+``` pascal
472+type
473+ { TMyListEnumerator }
474+
475+ TMyListEnumerator = object
476+ private
477+ FCurrent: Integer;
478+ public
479+ constructor Create;
480+ destructor Destroy;
481+ function StepNext: Boolean; enumerator MoveNext;
482+ property Value: Integer read FCurrent; enumerator Current;
483+ end;
484+
485+ TMyList = class
486+ end;
487+
488+{ TMyListEnumerator }
489+
490+constructor TMyListEnumerator.Create;
491+begin
492+ FCurrent := 0;
493+end;
494+
495+destructor TMyListEnumerator.Destroy;
496+begin
497+ inherited;
498+end;
499+
500+function TMyListEnumerator.StepNext: Boolean;
501+begin
502+ inc(FCurrent);
503+ Result := FCurrent <= 3;
504+end;
505+
506+operator enumerator (AList: TMyList): TMyListEnumerator;
507+begin
508+ Result.Create;
509+end;
510+
511+var
512+ List: TMyList;
513+ i: integer;
514+begin
515+ List := TMyList.Create;
516+ for i in List do
517+ WriteLn(i);
518+ List.Free;
519+end.
520+```
521+
522+## 提案された拡張
523+
524+### 使用可能ならば加算子の位置を得る
525+
526+イテレータから現在のアイテムを除き、どの位置をも得ることができる。時に他のデータ、例えば現在のインデクス、が有用だろう:
527+
528+``` pascal
529+type
530+ TUTF8StringEnumerator = class
531+ private
532+ FByteIndex: Integer;
533+ FCharIndex: Integer;
534+ public
535+ constructor Create(const A: UTF8String);
536+ function Current: UTF8Char;
537+ function CurrentIndex: Integer;
538+ function MoveNext: Boolean;
539+ end;
540+
541+ operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
542+ begin
543+ Result := TUTF8String.Create(A);
544+ end;
545+
546+var
547+ s: UTF8String;
548+ ch: UTF8Char;
549+ i: Integer;
550+begin
551+
552+ // 上で議論したように非効率的である
553+ for i := 1 to Length(s) do
554+ Writeln(i, ': ', ch[i]);
555+
556+ // よいが、醜い
557+ i := 1;
558+ for ch in s do begin
559+ Writeln(i, ': ', ch);
560+ Inc(i);
561+ end;
562+
563+ // 提案された拡張
564+ for ch in s index i do
565+ Writeln(i, ': ', ch);
566+
567+ // 逆方向に走査するために提案された拡張(downtoと同等)
568+ for ch in reverse s do
569+ Writeln(i, ': ', ch);
570+
571+ // 提案されたインデクスの拡張
572+ for ch in reverse s index i do
573+ Writeln(i, ': ', ch);
574+end.
575+```
576+
577+インデクスは任意の型を返すように設計されている(即ち、整数でなくてもよい)。例えばツリーの走査の場合、インデクスは現在のルートからノードまでのパスを記述した配列を返すかもしれない。
578+
579+## 参考
580+
581+- <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
582+
583+---
584+*From the Free Pascal wiki, page "for-in loop/ja", as it stood on 2024-03-10 after a change by Ariben, 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