2011-11-08 2 views
6

私はEnumerableとEnumeratorの両方であるGenericPermutationsというクラスを持っています。その仕事は、順序付けられたオブジェクトのリストを取得し、順番に各順列を反復することです。C#クラスはIEnumerableであり、同時にIEnumeratorです。これに関する問題は何ですか?

例、このクラスの整数implemenationは、以下を反復処理できます。

GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 }); 
p.nextPermutation(); // 123 
p.nextPermutation(); // 132 
p.nextPermutation(); // 213 
// etc. 

それはあなたがオーバー列挙することができ、物事の「リスト」を含んでいるという意味で、だから、その列挙。これは、列挙子でもあります。その仕事は、次の順列を見つけることにあります。

問題:私は現在、このクラスにIEnumeratorとIEnumerableを統合しようとしています。私は、(IEnumerableとしてサブクラスを使用するのではなく)両方でなければならないようです。ここまでは、GetEnumeratorメソッドで新しいGenericPermutationオブジェクトを渡すことで、2つの列挙子を取得しようとする問題を回避しました。

これは悪い考えですか?他に何か考慮する必要がありますか?

+0

enumerableインスタンスが、それから要求されたすべての列挙子の1つのインスタンスでもある場合、どうやってそれらの間に状態を維持しますか? –

+0

私の考えは、列挙子が要求されるたびにオブジェクトの新しいコピーを作成し、そのコピーを列挙子として渡すことでした。したがって、新しい列挙子が要求された場合、元の状態の現在の列挙子のコピーが渡されます。 – jtfairbank

答えて

8

汎用バージョンのIEnumerableIEnumeratorを使用して混乱(?)を減らしてください。

置換の列挙はIEnumerable<IEnumerable<T>>です。

IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence) 
{ 
    return new Permuter<T>(sequence); 
} 

さらに
public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... } 

のようなものを持っているので、あなたは、私はシングルタイプはIEnumerable<T>IEnumerator<T>の両方を実装し、複数のケースを見てきたかもしれません。そのGetEnumeratorメソッドは単にreturn this;でした。

私はこのような型は構造体である必要があると思います。なぜなら、最初の列挙が完了する前にGetEnumerator()を呼び出すと、あらゆる種類の問題が発生するからです。

EDIT:{1、2、3}、出力される入力シーケンスを仮定するパーミュータ

var permuter = GetPermutations(sequence); 
foreach (var permutation in permuter) 
{ 
    foreach (var item in permutation) 
     Console.Write(item + "; "); 
    Console.WriteLine(); 
} 

を消費

1; 2; 3; 
1; 3; 2; 
2; 1; 3; 
2; 3; 1; 
3; 1; 2; 
3; 2; 1; 

EDIT:ここ

超非効率的です提案を説明する実装:

public class Permuter<T> : IEnumerable<IEnumerable<T>> 
{ 
    private readonly IEnumerable<T> _sequence; 

    public Permuter(IEnumerable<T> sequence) 
    { 
     _sequence = sequence; 
    } 

    public IEnumerator<IEnumerable<T>> GetEnumerator() 
    { 
     foreach(var item in _sequence) 
     { 
      var remaining = _sequence.Except(Enumerable.Repeat(item, 1)); 
      foreach (var permutation in new Permuter<T>(remaining)) 
       yield return Enumerable.Repeat(item, 1).Concat(permutation); 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 
+0

ネストされたfor-eachループやその他の状況で問題が発生する可能性があるため、GetEnumeratorメソッドで 'this'を渡すのは好きではありません。あなたの最初の提案は興味深いようですが、もう少し拡張することができますか?私は 'IEnumerable >'に混乱しています。 – jtfairbank

+1

あなたは何について混乱していますか?シーケンス{1,2,3}の順列は6つのシーケンス({1,2,3}、{1,3,2}など)を含む。これは一連の配列と考えることができます。コード例を追加します。 – phoog

+0

ああ、私はあなたが意味するものを参照してください。しかし、シーケンス自体、それを生成する順列だけを列挙する必要はありません。 – jtfairbank

0

1つのオブジェクトがIEnumerator<T>IEnumerable<T>の両方として動作する可能性がありますが、一般的に、オブジェクトが気まぐれなセマンティクスを避けるようなやり方で行うことは困難です。 がステートレスにならない限り(MoveNext()は常にfalseを返し、無限リピート列挙子の場合はMoveNext()は常にtrueを返しますが、Currentは常に同じ値を返します)、GetEnumerator()を呼び出すたびに別個のオブジェクトインスタンスを返します。そのインスタンスの実装にはほとんど価値がない可能性があります。IEnumerable<T>

値型がIEnumerable<T>IEnumerator<T>を実装したが、そのGetEnumerator()メソッドの戻りthisを有する、GetEnumeratorへの各呼び出しは、個別のオブジェクトのインスタンスを返す要求を満たすが、値型が可変インターフェースを実装有する一般に危険であろう。値のタイプがIEnuerator<T>にボックス化され、ボックス化されていない場合は、クラス型オブジェクトとして動作しますが、単純にクラス型オブジェクトであってはならない本当の理由はありません。

C#のイテレータは、IEnumerable<T>IEnumerator<T>の両方を実装するクラスオブジェクトとして実装されていますが、意味の正確さを保証するためのかなりの論理を含んでいます。正味の効果は、1つのオブジェクトが両方のインターフェイスを実装することにより、生成されるコードのかなりの複雑さと引き換えに、パフォーマンスのわずかな改善と、その動作におけるいくつかの意味的な奇妙さがもたらされることです。人間が読めるようにする必要があるコードでは、このアプローチを推奨しません。 IEnumerator<T>IEnumerable<T>の側面では主に異なるフィールドが使用され、別のクラスを使用する場合は結合されたクラスには「スレッドID」フィールドが必要であるため、パフォーマンスを向上させるには同じ両方のインターフェースに実装するオブジェクトは限定されています。コンパイラを複雑にすると、何百万のイテレータ・ルーチンに対してわずかなパフォーマンス向上が見込まれますが、1つのルーチンのパフォーマンスを向上させる価値はありません。

関連する問題