イテレータとジェネレータの違いは何ですか?
9 に答える
ジェネレーターはイテレーターですが、すべてのイテレーターがジェネレーターというわけではありません。
イテレータは通常、ストリームから次の要素を取得するためのnextメソッドを持つものです。ジェネレーターは、関数に関連付けられたイテレーターです。
たとえば、Python のジェネレーター:
def genCountingNumbers():
n = 0
while True:
yield n
n = n + 1
これには、反復するために無限の数をメモリに格納する必要がないという利点があります。
これを他のイテレータと同じように使用します。
for i in genCountingNumbers():
print i
if i > 20: break # Avoid infinite loop
配列を反復処理することもできます。
for i in ['a', 'b', 'c']:
print i
ここにはあまりにも多くの Python があり、ジェネレーターが無限イテレーターを実装する唯一の方法であると言っている人が多すぎます。C# で実装した例 (すべての自然数の 2 乗) を次に示します。ExplicitSquares は反復子 (C# では IEnumerator と呼ばれます) を明示的に実装します。ImplicitSquares は、ジェネレーターを使用して同じことを行います。どちらも無限反復子であり、バッキング コレクションはありません。唯一の違いは、ステート マシンが記述されているか、代わりにジェネレータが使用されているかです。
using System.Collections;
using System.Collections.Generic;
using System;
class ExplicitSquares : IEnumerable<int>
{
private class ExplicitSquaresEnumerator : IEnumerator<int>
{
private int counter = 0;
public void Reset()
{
counter = 0;
}
public int Current { get { return counter * counter; }}
public bool MoveNext()
{
counter++;
return true;
}
object IEnumerator.Current { get { return Current; } }
public void Dispose(){}
}
public IEnumerator<int> GetEnumerator()
{
return new ExplicitSquaresEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class ImplicitSquares : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
int counter = 1;
while(true)
{
int square = counter * counter;
yield return square;
counter++;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class AllSquares
{
private static readonly int MAX = 10;
public static void Main()
{
int i = 0;
foreach(int square in new ExplicitSquares())
{
i++;
if(i >= MAX)
break;
Console.WriteLine(square);
}
Console.WriteLine();
int j = 0;
foreach(int square in new ImplicitSquares())
{
j++;
if(j >= MAX)
break;
Console.WriteLine(square);
}
}
}
ジェネレーターはイテレーターの実装です。これは通常、呼び出し元に 1 つではなく複数の値を与えるルーチンです。
C#で
// yield-example.cs
using System;
using System.Collections;
public class List
{
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8))
{
Console.Write("{0} ", i);
}
}
}
ジェネレーターは、呼び出されるたびに値を返すイテレーターとして動作できる特別な関数です。これは関数であるため、必要に応じて各値を計算できます。また、これは特別なので、最後に呼び出されたときの状態を記憶できるため、結果のコードは非常に単純に見えます。
たとえば、Python のこのジェネレーターは整数のシーケンスを生成します。
def integers():
int n = 0
while True:
yield n
n += 1
この例で重要なのはyield nステートメントです。関数は値を返し、次に呼び出されたときにその時点から続行します。
このリンクには、Python のジェネレーターの詳細な説明があります: リンク テキスト
通常、反復子は既存のシーケンス (配列やリストなど) を走査し、ジェネレーターは要求ごとに新しい値を計算します。
反復子は、配列、リンク リスト、ツリー、ハッシュ マップなど、コレクション内のオブジェクトを反復処理するために使用されます。たくさんのオブジェクトがあり、それらのそれぞれで何かをしたいとします。
ジェネレーターは、オブジェクトの有限コレクションからアイテムを返すだけではありません。代わりに、オンザフライで生成します。コレクションを繰り返し処理しているときに作成されたコレクションのイテレーターとして概念化でき、サイズが有限ではない場合があります。
たとえば、2 から無限大までの素数を吐き出すジェネレーターを作成できます。「すべての素数」のコレクションを取得して、反復子で反復処理する方法はありません。発電機が必要です。
または、整数を取り、その数の因数を一度に 1 つずつ生成するジェネレーターを使用することもできます。ここでジェネレーターを使用すると、事前にすべての要素にメモリを割り当てることなく、要素を 1 つずつ調べることができるため、便利です。また、リスト全体を事前に生成するのではなく、生成されたときにそれらを使用することもできます。Python でのこのようなジェネレータの例を次に示します。
def factors(n):
for i in xrange(1, n+1):
if n % i == 0:
yield i
for n in factors(1234567890):
print n
これを実行すると、計算された係数が表示されます。すべての因子の完全なリストを実際にメモリに保持する必要はありません。
イテレータは、通常、アイテムのコレクションを移動するために使用されます。多くの場合、MoveNext() および Current() メソッドがあります。MoveNext() は、ポインターを次のコレクション項目にシフトし (可能な場合)、成功に基づいて true/false を返します。Current() は実際の値を提供します。
ジェネレーターは反復子の実装ですが、既存のコレクションを指す代わりに、MoveNext() 呼び出しごとに新しい項目を作成します。