2011-11-08 12 views
6

Tengo una clase llamada GenericPermutations que es enumerable y enumerador. Su trabajo es tomar una lista ordenada de objetos e iterar a través de cada permutación de ellos en orden.La clase C# es IEnumerable Y un IEnumerator al mismo tiempo. ¿Cuáles son los problemas con esto?

ejemplo, un número entero implemenation de esta clase podría iterar a través de los siguientes:

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

Así que es numerable en el sentido de que contiene una 'lista' de cosas que se pueden enumerar de nuevo. También es un enumerador, porque su trabajo implica encontrar la siguiente permutación.

EL PROBLEMA: Actualmente estoy tratando de integrar IEnumerator y IEnumerable con esta clase, y me parece que debería ser a la vez (en lugar de utilizar una subclase como el IEnumerable). Hasta ahora, he evitado el problema al intentar obtener dos enumeradores al pasar un nuevo objeto GenericPermutation en el método GetEnumerator.

¿Es esta una mala idea? ¿Algo más que deba considerar?

+0

Si su instancia enumerable es también la única instancia para todos los enumeradores solicitados, ¿cómo va a mantener el estado entre ellos? –

+0

Mi idea era crear una nueva copia de mi objeto cada vez que se solicita un enumerador, y pasar esa copia como el enumerador. Entonces, si se solicita un nuevo enumerador, se pasa una copia del enumerador actual en su estado original. – jtfairbank

Respuesta

8

Reduzca su confusión (?) Mediante el uso de las versiones genéricas de IEnumerable y IEnumerator.

Una permutación enumerable es IEnumerable<IEnumerable<T>>. Por lo que podría tener algo como

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

y

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

Por otra parte, he visto más de un caso en el que un solo tipo implementado tanto IEnumerable<T> y IEnumerator<T>; su método GetEnumerator era simplemente return this;.

Creo que un tipo así tendría que ser una estructura, porque si fuera una clase tendría todo tipo de problemas si llamara a GetEnumerator() por segunda vez antes de que se completara la primera enumeración.

EDIT: El consumo de la permutador

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

Suponiendo que la secuencia de entrada es {1, 2, 3}, la salida es

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

EDIT:

Aquí hay una super-ineficiente implementación para ilustrar la sugerencia:

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

No me gusta simplemente pasar 'this' en el método GetEnumerator porque puede haber problemas anidados en cada bucle y en otras situaciones. Sin embargo, su primera sugerencia parece interesante, ¿podría ampliarla un poco más? Estoy confundido por el 'IEnumerable >'. – jtfairbank

+1

¿De qué estás confundido? Las permutaciones de la secuencia {1, 2, 3} comprenden seis secuencias ({1, 2, 3}, {1, 3, 2}, etc.); esto se puede pensar como una secuencia de secuencias. Agregaré un ejemplo de código. – phoog

+0

Ah, ya veo lo que quieres decir. Sin embargo, nunca debería tener que enumerar las secuencias en sí, solo las permutaciones que las generan. – jtfairbank

0

Es posible que un objeto se comporte como un IEnumerator<T> y IEnumerable<T>, pero generalmente es difícil para un objeto hacerlo de tal manera que se evite una semántica estrafalaria; a menos que el IEnumerator<T> sea apátrida (por ejemplo, un enumerador vacío, donde MoveNext() siempre devuelve falso, o un enumerador de repetición sin fin, donde MoveNext() no hace nada pero siempre devuelve verdadero, y Current siempre devuelve el mismo valor), cada llamada al GetEnumerator() devuelve una instancia de objeto distinta, y es probable que haya poco valor para que esa instancia implemente IEnumerable<T>.

Tener un tipo de valor implementar IEnumerable<T> y IEnumerator<T>, y que tiene su GetEnumerator() método retorno this, satisfaga el requisito de que cada llamada a GetEnumerator retorno una instancia de objeto distinto, pero que tienen los tipos de valor implementan interfaces de mutables es generalmente peligroso. Si un tipo de valor está encasillado en IEnuerator<T> y nunca se ha desempaquetado, se comportará como un objeto de tipo clase, pero no hay una razón real por la que no debería haber sido simplemente un objeto de clase.

Los iteradores en C# se implementan como objetos de clase que implementan IEnumerable<T> y IEnumerator<T>, pero incluyen un poco de lógica sofisticada para garantizar la corrección semántica. El efecto neto es que tener un objeto que implemente ambas interfaces ofrece una ligera mejora en el rendimiento, a cambio de un poco de complejidad en el código generado, y cierta rareza semántica en su comportamiento IDisposable. No recomendaría este enfoque en ningún código que deba ser legible por humanos; dado que los aspectos IEnumerator<T> y IEnumerable<T> de la clase usan principalmente campos diferentes, y dado que una clase combinada necesita tener un campo "thread-id" que no sería necesario si se usan clases separadas, la mejora del rendimiento puede lograrse utilizando el mismo objeto para implementar para ambas interfaces es limitado. Vale la pena hacerlo quizás si agregar la complejidad al compilador proporcionará una ligera mejora en el rendimiento de millones de rutinas de iteradores, pero no vale la pena hacerlo para mejorar el rendimiento de una rutina.

Cuestiones relacionadas