イテレータ
イテレータ(英語: iterator)とは、プログラミング言語において配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化である。実際のプログラミング言語では、オブジェクトまたは文法などとして現れる。JISでは反復子(はんぷくし)と翻訳されている[1][2]。
ジェネレータ (プログラミング) の記事も参照のこと。
種類
[編集]この節には独自研究が含まれているおそれがあります。 |
内部イテレータ
[編集]hashなどのオブジェクトが高階関数として実装しているイテレータのことを内部イテレータという。 主にオブジェクト指向プログラミングの性質に基づき通常外部から明示的に使用することはないイテレータを指す。[要出典]
各言語における例
[編集]C++
[編集]C++では、STLが外部イテレータの枠組みを定義している。この枠組みはポインタと構文上の互換性を持つよう定められているため、ポインタを用いるコードと同等のコードでイテレータを使用することができる。
template<typename InputIterator> void printall(InputIterator first, InputIterator last) { for (; first != last; ++first) { std::cout << *first << std::endl; } }
C++ではイテレータの種類によって使用できる演算子に違いがある。例えば、std::vector
コンテナのイテレータのような「ランダムアクセスイテレータ」と、std::list
コンテナのイテレータのような「双方向イテレータ」は、共にインクリメント/デクリメント演算子を用いて直後/直前(次/前)の要素を指すことができる。しかし、任意の幅の加算/減算はランダムアクセスイテレータでのみ定義されている。ランダムアクセスイテレータは双方向イテレータの一種だが、イテレータの進行操作は定数時間で実行できることが要件として定められている[3]。std::list
コンテナの要素は、前後の要素へのポインタしか保持していないためシーケンシャルアクセスしかできず、イテレータの進行操作が定数時間で実行できないため、ランダムアクセスイテレータをサポートすることができない。
もともとC++のイテレータはテンプレートを利用した静的ダックタイピングによるものであり、各イテレータの要件定義は規格で文書化されていたものの、言語構文としてはサポートされていなかった。しかし、C++20ではコンセプト (concept
) をサポートしたことにより、std::random_access_iterator
やstd::bidirectional_iterator
などの形で満たすべき要件をコードとして書き下すことが可能になった[4][5]。
Delphi
[編集]Delphiでは、バージョン2005よりfor-in
構文によるイテレータがある。ユーザーによるイテレータはMoveNext
メソッドやCurrent
プロパティを任意のクラス等に実装することで定義でき、型に厳格なPascal系言語ながらこれらを実装するだけでfor-in
により認識されるというダックタイピングにも似た仕組みとなっている。
for item in items do Writeln(item);
Java
[編集]Javaでは、java.util.Iterator
インターフェイス族を実装するオブジェクトを外部イテレータとして扱うことができる。Java 1.5以降のIterator
はジェネリクスに対応している[注釈 1]。
import java.util.*; // ... final List list = new ArrayList(); // Java 1.5 以降は以下のように書くこともできる。 //final List<Object> list = new ArrayList<Object>(); // Java 1.7 以降は以下のように書くこともできる。 //final List<Object> list = new ArrayList<>(); list.add("hoge"); list.add(Integer.valueOf(100)); list.add(Double.valueOf(-0.5)); final Iterator it = list.iterator(); // Java 1.5 以降は以下のように書くこともできる。 //final Iterator<Object> it = list.iterator(); while (it.hasNext()) { final Object obj = it.next(); System.out.println(obj.toString()); }
Java 1.5以降では、java.lang.Iterable
インターフェイスを実装すると、拡張for文(foreach文)の対象にすることができる。
// java.util.List は Iterable を実装している。 for (final Object obj : list) { System.out.println(obj.toString()); }
Perl
[編集]Perlには、foreach
、each
といった繰り返しのキーワードがある。 他に、Tie機能(変数操作のオーバーロード)でユーザーデータに対するイテレータを定義できる。
# foreachを使った例。配列・リストに対する反復。 foreach my $element(@array){ print $element, "\n"; }
# eachを使った例。ハッシュ(連想配列)に対する反復。 while(my($key, $value) = each %hash){ print "$key=$value\n"; }
PHP
[編集]PHPではIteratorインターフェイスを実装することにより、任意のイテレータを定義することができる。Iteratorインターフェイスを実装したオブジェクトをforeach
やwhile
で使用することで反復を行うことができる。 また、配列はIteratorインターフェイスを暗黙に実装する。
# foreachを使った例。配列・連想配列・オブジェクト等に対し全く同じように使用できる。 foreach ( $elements as $key=>$value ){ print $key . "=" . $value . " \n"; }
Python
[編集]Pythonは次の要素を返す__next__()
メソッドを持つオブジェクトを外部イテレータとして使う。コンテナオブジェクトの__iter__()
メソッドがイテレータを返す。(便宜上、イテレータの__iter__()
は自身を返す)
通常のプログラミングでは、obj.__iter__()
のように直接呼ぶのではなく、組込み関数iterを使ってiter(obj)
のようにする。同様に、通常の用法で呼ぶことを前提とした場合は__next__()
ではなくnext()
を使う。for文(Foreach文)はイテレータが使える場合はイテレータを使うが、そうでないコンテナオブジェクトに対しては直接、__getitem__()
メソッドにより要素を取得し繰返しを実行する。
Pythonのfor文においてはiterableを範囲にとって、暗黙的にiteratorを利用する点を指して内部イテレータと呼ばれる場合もある。[要出典]
cont = iteratable_container() # イテレータを直接使う it = iter(cont) while 1: try: print it.next() except StopIteration: # 要素が残っていないならば、 # next()はStopIteration例外を発生させる break # for文で使う for element in cont: print element
また、Pythonには一種のコルーチンを記述できるジェネレータもある。ジェネレータはイテレータを返す関数で、yield文により、__next__()
で実行される手続きを次々と記述できる。
def fruit_generator(): yield 'banana' # 最初の __next__() によりここまで実行され 'banana' を返す yield 'apple' # 次の __next__() によりここまで実行され 'apple' を返す yield 'orange' # 3回目の __next__() によりここまで実行され 'orange' を返す for fruit in fruit_generator(): print(fruit) it = fruit_generator() print(next(it)) print(next(it)) print(next(it)) print(next(it)) # この行で StopIteration 例外になる
Ruby
[編集]RubyではEnumerableが、eachなどのイテレートするメソッドを持っている内部イテレータである。each
メソッド呼出しに { ... }
という書式で「ブロック」を書くと、その中の手続きが繰返し実行される。
class MyObj def my123 yield 1 yield 2 yield 3 end end arr = ["a", "b", "c"] arr.each do |x| p x end obj = MyObj.new obj.my123 do |x| p x end
.NET言語
[編集]C#、VB.NETなどの.NET Frameworkに準拠する.NET言語において、反復子 (iterator) は値の順序付き列を産出 (yield) する文のブロック(狭義ではyield文を含むメソッド)を意味する[6][7][8]。これを反復子ブロック (iterator block) とも呼ぶ[9]。また、コレクションに対する列挙操作を行なう機能を提供するための媒介インターフェイス[10]を列挙子 (enumerator) と呼び、IEnumerator
インターフェイスによって表す。IEnumerator
インターフェイスはMoveNext()
メソッドを定義しており、このメソッドを使用することによりコレクション中の次の要素に進むと同時に、コレクションの末尾に到達するかどうかを判定する。Current
プロパティを使用することによってコレクション内部の要素を取得する。イテレータを最初の要素に戻す方法としてReset()
メソッドが定義されるが、常に使用可能であるとは限らない[11]。
列挙子を得るには通例IEnumerable
インターフェイスを実装するオブジェクトのGetEnumerator()
メソッドを呼び出す。一般的にコレクション クラスはこのIEnumerable
インターフェイスを実装する。GetEnumerator()
を明示的に呼び出さず、foreach
文を代わりに使用することもできる(GetEnumerator()
はコンパイラによって暗黙的に呼び出される)。IEnumerator
およびIEnumerable
インターフェイスは、.NET 2.0でジェネリック版 (System.Collections.Generic
) として拡張された。
C# 2.0
[編集]// 明示的な使い方 IEnumerator<MyType> iter = list.GetEnumerator(); while (iter.MoveNext()) { Console.WriteLine(iter.Current); } // 暗黙的な使い方 foreach (MyType value in list) { Console.WriteLine(value); }
Visual Basic 8.0
[編集]' 明示的な使い方 Dim iter As IEnumerator(Of MyType) = list.GetEnumerator() Do While iter.MoveNext() Console.WriteLine(iter.Current) Loop ' 暗黙的な使い方 For Each value As MyType In list Console.WriteLine(value) Next value
yield文
[編集]C# 2.0およびVB.NET 11は反復子の形でジェネレータ (generator) をサポートする。ジェネレータはIEnumerator
またはIEnumerable
を返すよう宣言されたメソッドであるが、オブジェクトインスタンスを返す代わりに要素のシーケンスを生成するためのyield return
ステートメントを使用する。yieldステートメントを用いて記述されたジェネレータはコンパイラによって、適切なインターフェイスを実装する新しいクラスに変換される。ただし、ジェネレータ(反復子)はIEnumerator.Reset()
メソッドをサポートしない。
// 反復子の記述例。 static IEnumerable<int> MyIteratorMethod() { yield return 1; yield return -1; yield return 0; yield break; } IEnumerable<int> elements = MyIteratorMethod(); // この時点では、まだメソッドの本体は実行されない。 // 列挙によりメソッドの本体が順次「遅延実行」される(中断と再開を繰り返す)。 foreach (int element in elements) { Console.WriteLine(element); }
Shared Iterator Function MyIteratorMethod() As IEnumerable(Of Integer) Yield 1 Yield -1 Yield 0 Return End Function Dim elements As IEnumerable(Of Integer) = MyIteratorMethod() For Each element As Integer In elements Console.WriteLine(element) Next
D言語
[編集]D言語では、標準ライブラリにレンジ (Range) というイテレータが定義されており、規定されたインターフェイスを持っているオブジェクトなら何でもレンジとして扱うことができる。
foreach (item; range) writeln(item);
脚注
[編集]注釈
[編集]- ^ Javaのジェネリクスは型消去による実装であり、型システムの上では
Iterator
もIterator<E>
も同じ型である。
出典
[編集]- ^ JISC 日本産業標準調査会 JISX3014:2003『プログラム言語C++』、JISX3015:2008『プログラム言語C#』、JIS X 3005-10:2003『データベース言語SQL 第10部』など。
- ^ 繰り返し子(くりかえしし)という訳もあるが一般的ではない。「Rubyプログラミング入門」著者: 原信一郎、出版: オーム社、p.197。
- ^ C++ named requirements: LegacyRandomAccessIterator - cppreference.com
- ^ random_access_iterator - cpprefjp C++日本語リファレンス
- ^ std::random_access_iterator - cppreference.com
- ^ JIS X 3015「プログラム言語C#」p.64より引用。
- ^ 反復子 (C#) | Microsoft Docs
- ^ yield (C# リファレンス) | Microsoft Docs
- ^ yield (C# リファレンス) | Microsoft Docs - Visual Studio 2008
- ^ IEnumerable・IEnumerator - Programming/.NET Framework/列挙操作と列挙子 - 総武ソフトウェア推進所
- ^ IEnumerator.Reset Method (System.Collections) | Microsoft Docs