2010-08-28 12 views
5

Estoy teniendo un momento difícil con un problema aparentemente fácil y embarazoso. Todo lo que quiero es el siguiente elemento en un IEnumberable sin usar Skip (1) .Take (1) .Single(). Este ejemplo ilustra el problema básico.Alternativa a IEnumerable <T> .Skip (1) .Take (1) .Single()

private char _nextChar; 
private IEnumerable<char> getAlphabet() 
{ 
    yield return 'A'; 
    yield return 'B'; 
    yield return 'C'; 
} 
public void sortAlphabet() 
{ 
    foreach (char alpha in getAlphabet()) 
    { 
     switch (alpha) 
     { 
      case 'A': //When A pops up, I want to get the next element, ie 'B' 
       _nextChar = getAlphabet().Skip(1).Take(1).Single(); 
       break; 
      case 'B': //When B pops up, I want 'C' etc 
       _nextChar = getAlphabet().Skip(1).Take(1).Single(); 
       break; 
     } 
    } 
} 

Aparte de ser feo, este ejemplo funciona. Pero digamos que IEnumerable contiene 2 millones de elementos, entonces la declaración LINQ hace que el programa se ejecute insoportablemente lento. Lo que quiero es simple. Solo quiero el siguiente elemento en un IEnumberable <>. Todos mis problemas se resolverían si había una función como:

_nextChar = getAlphabet().moveNext() //or getNext() 

es mucho más preferible que la solución mantiene la misma estructura/diseño/funcionalidad del ejemplo sin embargo, yo soy flexible. Mi programa es un analizador de archivos, y entre los 2 millones de líneas de texto hay algunas claves como "dinero = 324" donde "dinero" y "324" son elementos vecinos en el IEnumberable y cuando el analizador encuentra "dinero" quiero " 324 ". (Que no lo hace:?. D Lo siento por el mal juego de palabras)

+0

Esto suena como una caja de FSM. – Necros

+2

Este código siempre recuperará el segundo elemento de la secuencia, es decir, 'B', no creo que sea eso lo que quiere ... –

+0

La respuesta de la respuesta ya ha respondido a su pregunta. Sin embargo, para referencia futura, en lugar de '.Take (1) .Single()' puede usar '.First()' que hace lo mismo. – Timwi

Respuesta

13

Todos mis problemas se resolverían si hubo una función como:

_nextChar = getAlphabet().moveNext() //or getNext()

hay una función exactamente así. ¡Simplemente pertenece a IEnumerator<T>, no a IEnumerable<T>!

private char _nextChar; 
private IEnumerable<char> getAlphabet() 
{ 
    yield return 'A'; 
    yield return 'B'; 
    yield return 'C'; 
} 

public void sortAlphabet() 
{ 
    using (var enumerator = getAlphabet().GetEnumerator()) 
    { 
     while (enumerator.MoveNext()) 
     { 
      char alpha = enumerator.Current; 
      switch (alpha) 
      { 
       case 'A': 
        if (enumerator.MoveNext()) 
        { 
         _nextChar = enumerator.Currrent; 
        } 
        else 
        { 
         // You decide what to do in this case. 
        } 
        break; 
       case 'B': 
        // etc. 
        break; 
      } 
     } 
    } 
} 

Sin embargo, esta es una pregunta para usted. ¿Es necesario que este código use un IEnumerable<char>, en lugar de un IList<char>? Pregunto porque, como si esto no fuera obvio, el código sería mucho más simple si tuviera acceso aleatorio a los ítems devueltos por getAlphabet por índice (y si alguien tiene la tentación de señalar que puede hacerlo con ElementAt, por favor, acaba de sacar esa idea de tu cabeza ahora mismo).

Es decir, tener en cuenta lo que el código se vería así en este caso:

private char _nextChar; 
private IList<char> getAlphabet() 
{ 
    return Array.AsReadOnly(new[] { 'A', 'B', 'C' }); 
} 

public void sortAlphabet() 
{ 
    IList<char> alphabet = getAlphabet(); 
    for (int i = 0; i < alphabet.Count - 1; ++i) 
    { 
     char alpha = alphabet[i]; 
     switch (alpha) 
     { 
      case 'A': 
       _nextChar = alphabet[i + 1]; 
       break; 
      case 'B': 
       // etc. 
       break; 
     } 
    } 
} 

no es mucho más fácil?

+0

Su primera respuesta es exactamente lo que estoy buscando (a veces una nueva perspectiva de algo es lo que uno está buscando). Sin embargo, estoy un poco confundido por su sugerencia. No quiero acceso aleatorio. Quiero, en este caso, las letras tal como vienen. Y, puedo estar equivocado, ¿no es getAlphabet() sosteniendo todo el contenido en la memoria? No quiero eso, ya que en mi caso getAlphabet() tendría 2 millones de líneas. –

+0

Ah, me acabo de dar cuenta de que podría haber algo de confusión. Lo del alfabeto fue solo un ejemplo de mi problema. En realidad, no hago nada con el alfabeto o el carboncillo para el caso. Si lo desea, puedo proporcionar el código relevante, pero ya respondió mi pregunta. –

+0

@Nick: no es necesario aclararlo. Incluso poco después de publicar mi sugerencia, recordé que había dicho 2 millones de artículos; así que, obviamente, almacenar el contenido en la memoria no tendría sentido en su caso. Sin embargo, dejo la idea ahí arriba, en caso de que alguien más se encuentre a sí mismo enfrentando un problema similar en un escenario donde el acceso a los miembros de la colección por índice podría ser una opción. –

4

Calculo desea que esta:

public void sortAlphabet() { 
     using (var enu = getAlphabet().GetEnumerator()) { 
      while (enu.MoveNext()) { 
       switch (enu.Current) { 
        case 'A': 
         enu.MoveNext(); 
         _nextChar = enu.Current; 
         break; 
       } 
      } 
     } 
    } 

Tenga en cuenta que esto consume el siguiente elemento, justo lo que quieres si leo su pregunta correcta.

+1

Realmente debería usar 'using' alrededor de' GetEnumerator' ... – Timwi

+1

Sí, la placa base se incendiará si lo olvida. –

+2

Siempre. La interfaz genérica IEnumerable <> hereda de IDisposable. –

1

Como se señaló en otra respuesta, no es un método MoveNext(), y tiene acceso a la misma para todos los enumerables través de la interfaz IEnumerator<T> devuelto por una llamada a IEnumerable<T>.GetEnumerator(). Sin embargo, trabajar con MoveNext() y Current puede parecer algo "de bajo nivel".

Si prefiere un bucle foreach para procesar su colección getAlphabet(), se podría escribir un método de extensión que devuelve los elementos de cualquier enumerable en pares de dos:

public static IEnumerable<T[]> InPairsOfTwo<T>(this IEnumerable<T> enumerable) 
{ 
    if (enumerable.Count() < 2) throw new ArgumentException("..."); 

    T lastItem = default(T); 
    bool isNotFirstIteration = false; 

    foreach (T item in enumerable) 
    { 
     if (isNotFirstIteration) 
     { 
      yield return new T[] { lastItem, item }; 
     } 
     else 
     { 
      isNotFirstIteration = true; 
     } 
     lastItem = item; 
    } 
} 

tendrá que utilizar la siguiente manera:

foreach (char[] letterPair in getAlphabet().InPairsOfTwo()) 
{ 
    char currentLetter = letterPair[0], 
     nextLetter = letterPair[1];   

    Console.WriteLine("# {0}, {1}", currentLetter, nextLetter); 
} 

Y se obtendría la siguiente salida:

# A, B 
# B, C 

(Tenga en cuenta que si bien el método de extensión anterior devuelve pares de dos elementos cada uno, los pares se superponen en un elemento. Básicamente obtienes cada artículo así como una mirada hacia adelante. Si desea que el método de extensión devuelva el último elemento por sí mismo, puede adaptarlo adaptando el método de almacenamiento en búfer utilizado.)

0

Como se mencionó anteriormente, una máquina de estado es ideal para estos casos. También coincide con la forma en que pensamos sobre el problema, por lo que la legibilidad es excelente. No estoy seguro de qué quiere hacer exactamente con el código, por lo que el siguiente ejemplo devuelve el siguiente carácter cuando lo encuentra. La máquina de estado se adapta bien a las tareas complejas, y se puede modelar y verificar manualmente en papel.

enum State 
{ 
    Scan, 
    SaveAndExit 
}; 

public void SortAlphabet() 
{ 
    State state = State.Scan; // initialize 

    foreach(char c in getAlphabet()) 
    { 
     switch (state): 
     { 
      case State.Scan: 
       if (c == 'A' || 
        c == 'B') 
        state = State.SaveAndExit; 
       break; 
      case State.SaveAndExit: 
       return (c); 
       break; 
     } 
    } 
} 
0

Su código volverá 'B' cada vez, debido a que llame getAlphabet(), que devuelve un nuevo IEnumerable cada vez.

En función de lo que intenta hacer, probablemente sugeriría utilizar la iteración basada en índices en lugar de un enumerador. Si usó MoveNext para obtener el siguiente elemento, arruinaría su ciclo, por lo que usar una recuperación basada en índices funcionaría más limpiamente con mucho menos sobrecarga.

0

Si está utilizando .NET 4.0, entonces lo que estamos tratando de lograr es muy simple:

var alphabet = getAlphabet(); 
var offByOneAlphabet = alphabet.Skip(1); 

foreach (var pair in alphabet.Zip(offByOneAlphabet, (a, b) => Tuple.Create(a, b))) 
    Console.WriteLine("Letter: {0}, Next: {1}", pair.Item1, pair.Item2); 

// prints: 
// Letter: A, Next: B 
// Letter: B, Next: C 

Si uso de cualquier cosa menos de .NET 4.0, su todavía muy fácil de define your own Zip function y la clase Tupla .

Cuestiones relacionadas