2011-11-08 7 views
6

Mam klasy o nazwie GenericPermutations, która jest zarówno przeliczalne i moduł wyliczający. Jego zadaniem jest pobranie uporządkowanej listy obiektów i iteracja po każdej ich permutacji w kolejności.Klasa C# to IEnumerable I IEnumerator w tym samym czasie. Jakie są tego problemy?

przykład implementacja całkowitą tej klasy mógłby iterację następujące:

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

więc jego enumerable w tym sensie, że zawiera „listę” rzeczy można wyliczyć drugą. Jest to także moduł wyliczający, ponieważ jego zadaniem jest znalezienie kolejnej permutacji.

problem: Jestem obecnie próbuje zintegrować IEnumerator i IEnumerable z tej klasy, i wydaje mi się, że powinna być zarówno (zamiast przy użyciu klasy sub jako IEnumerable). Do tej pory uniknąłem problemu, próbując pobrać z niego dwa moduły wyliczające, przekazując nowy obiekt GenericPermutation w metodzie GetEnumerator.

Czy to zły pomysł? Coś jeszcze powinienem wziąć pod uwagę?

+0

Jeśli twoja wyliczalna instancja jest również jedną instancją dla wszystkich wyliczanych z niej enumeratorów, jak zamierzasz zachować stan między nimi? –

+0

Mój pomysł polegał na tym, że za każdym razem, gdy żądano modułu wyliczającego, utworzę nową kopię mojego obiektu i przekazuję tę kopię jako moduł wyliczający. Jeśli więc zostanie zamówiony nowy moduł wyliczający, zostanie przesłana kopia bieżącego modułu wyliczającego w jego pierwotnym stanie. – jtfairbank

Odpowiedz

8

Zredukuj zamieszanie (?) Za pomocą generycznych wersji IEnumerable i IEnumerator.

Permutacja przeliczalna to IEnumerable<IEnumerable<T>>. Więc może masz coś podobnego

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

i

public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... } 

Ponadto, widziałem więcej niż jeden przypadek, w którym jeden rodzaj realizowanych zarówno IEnumerable<T> i IEnumerator<T>; jego metoda GetEnumerator była po prostu return this;.

Myślę, że taki typ musiałby być jednak strukturą, ponieważ gdyby była klasą, miałbyś wiele problemów, gdyby wywołać funkcję GetEnumerator() po raz drugi przed zakończeniem pierwszego wyliczenia.

Edycja: Spożywanie tej permuter

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

Zakładając sekwencji wejściowej jest {1, 2, 3} wyjście

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

Edycja:

Oto bardzo nieefektywne implementacja w celu zilustrowania sugestii:

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

Nie lubię po prostu przekazywać 'this' w metodzie GetEnumerator, ponieważ mogą występować problemy zagnieżdżone dla każdej pętli i innych sytuacji. Twoja pierwsza sugestia wygląda jednak interesująco, możesz ją nieco rozszerzyć? Jestem zdezorientowany przez "IEnumerable >". – jtfairbank

+1

O co się martwisz? Permutacje sekwencji {1, 2, 3} obejmują sześć sekwencji ({1, 2, 3}, {1, 3, 2}, itd.); można to traktować jako sekwencję sekwencji. Dodam przykład kodu. – phoog

+0

Ah Widzę, co masz na myśli. Jednak nie powinienem był nigdy wymieniać samych sekwencji, tylko permutacji, które je generują. – jtfairbank

0

Jest możliwe, aby jeden obiekt zachowywał się zarówno jako obiekt IEnumerator<T>, jak i IEnumerable<T>, ale na ogół trudno jest obiektowi zrobić to w taki sposób, aby uniknąć dziwnych semantyki; chyba że IEnumerator<T> będzie bezstanowe (np. pusty moduł wyliczający, gdzie MoveNext() zawsze zwraca wartość false lub moduł wyliczający nieskończone liczby, gdzie MoveNext() zawsze zwraca wartość true, a Current zawsze zwraca tę samą wartość), każde wywołanie do GetEnumerator() musi zwraca odrębną instancję obiektu i prawdopodobnie nie ma dużej wartości, gdy instancja implementuje IEnumerable<T>.

uwzględniając typ wartości realizacji IEnumerable<T> i IEnumerator<T> i mająca GetEnumerator() Sposób powrotu this, by spełnić wymaganie, aby każde wywołanie GetEnumerator powrotu odrębny przykład obiekt, ale o typy wartości realizacji zmiennego interfejsów jest zazwyczaj niebezpieczne. Jeśli typ wartości jest ustawiony na IEnuerator<T> i nigdy nie został rozpakowany, zachowuje się jak obiekt typu klasowego, ale nie ma prawdziwego powodu, dla którego nie powinien być po prostu obiektem klasy.

Iteratory w języku C# są implementowane jako obiekty klasy, które implementują zarówno IEnumerable<T>, jak i IEnumerator<T>, ale zawierają dość wyszukaną logikę, aby zapewnić semantyczną poprawność. Efektem sieci jest to, że mając jeden obiekt zaimplementować oba interfejsy oferuje niewielką poprawę wydajności, w zamian za dość złożoną złożoność generowanego kodu i pewne semantyczne dziwactwo w ich zachowaniu IDisposable. Nie polecałbym tego podejścia w żadnym kodzie, który musi być czytelny dla człowieka; ponieważ aspekty klasy IEnumerator<T> i IEnumerable<T> używają głównie różnych pól, a ponieważ klasa łączona musi mieć pole "id wątku", które nie byłoby potrzebne, gdyby korzystano z oddzielnych klas, poprawa wydajności można osiągnąć, stosując to samo obiekt do zaimplementowania dla obu interfejsów jest ograniczony. Warto być może, jeśli dodanie komplikatora do złożoności zapewni niewielką poprawę wydajności milionów procedur iteracyjnych, ale nie warto tego robić, aby poprawić wydajność jednej rutyny.

Powiązane problemy