2010-08-28 20 views
5

Je rencontre des difficultés avec un problème apparemment facile et embarrassant. Tout ce que je veux, c'est l'élément suivant dans un IEnumberable sans utiliser Skip (1) .Take (1) .Single(). Cet exemple illustre le problème de base.Alternative à 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; 
     } 
    } 
} 

plus d'être laid, cet exemple fonctionne. Mais disons que l'IEnumerable contient 2 millions d'éléments, alors l'instruction LINQ rend le programme incroyablement lent. Ce que je veux est simple. Je veux juste l'élément suivant dans un IEnumberable <>. Tous mes problèmes seraient résolus s'il y avait une fonction comme:

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

Il est bien préférable que la solution conserve la même structure/layout/fonctionnalité de l'exemple cependant, je suis flexible. Mon programme est un analyseur de fichiers, et parmi les 2 millions de lignes de texte sont des clés comme "money = 324" où "money" et "324" sont des éléments voisins dans IEnumberable et quand l'analyseur rencontre "money" je veux " 324 ". (Qui n'a pas:. D Désolé pour le mauvais jeu de mots)

+0

Cela ressemble à un cas de FSM. – Necros

+2

Ce code récupérera toujours le deuxième élément de la séquence, c'est-à-dire 'B', je ne pense pas que ce soit ce que vous voulez ... –

+0

La réponse la plus demandée a déjà répondu à votre question. Cependant, pour référence future, au lieu de '.Take (1) .Single()' vous pouvez utiliser '.First()' qui fait la même chose. – Timwi

Répondre

13

Tous mes problèmes seraient résolus si il y avait une fonction comme:

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

Il y a une fonction exactement comme ça. Il appartient juste à IEnumerator<T>, pas 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; 
      } 
     } 
    } 
} 

Voici une question pour vous, cependant. Est-il nécessaire que ce code utilise un IEnumerable<char>, plutôt qu'un IList<char>? Je demande parce que, comme si ce n'était pas évident, le code serait beaucoup plus simple si vous avez un accès aléatoire aux éléments retournés par getAlphabet par index (et si quelqu'un est tenté de souligner que vous pouvez le faire avec ElementAt, s'il vous plaît, juste sortez cette idée de votre tête en ce moment).

Je veux dire, pensez à ce que le code ressemblerait dans ce cas:

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; 
     } 
    } 
} 

est-ce pas beaucoup plus facile?

+0

Votre première réponse est exactement ce que je cherche (parfois, une nouvelle perspective sur quelque chose est ce que l'on cherche). Cependant, je suis légèrement confus par votre suggestion. Je ne veux pas d'accès aléatoire. Je veux, dans ce cas, les lettres comme elles viennent. Et, je peux me tromper, n'est pas getAlphabet() tenant le contenu entier dans la mémoire? Je ne le veux pas, car dans mon cas, getAlphabet() contiendrait 2 millions de lignes. –

+0

Ah je viens de réaliser qu'il pourrait y avoir une certaine confusion. L'affaire de l'alphabet n'était qu'un exemple de mon problème. En réalité, je ne fais rien avec l'alphabet ou l'omble d'ailleurs. Si souhaité, je peux fournir un code pertinent, mais vous avez déjà répondu à ma question. –

+0

@Nick: Pas besoin de plus de clarification. Même après avoir posté ma suggestion, je me suis souvenu que vous aviez dit 2 millions d'articles; il est donc évident que stocker le contenu dans la mémoire n'aurait aucun sens dans votre cas. Je laisse l'idée là-bas, cependant, au cas où quelqu'un d'autre le trouverait confronté à un problème similaire dans un scénario où l'accès aux membres de la collection par index pourrait être une option. –

4

Je pense que vous voulez ceci:

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

Notez que ce consomme l'élément suivant, tout ce que vous voulez si je lis bien compris votre question.

+1

Devrait vraiment utiliser 'using' autour de' GetEnumerator' ... – Timwi

+1

Oui, la carte mère prendra feu si vous oubliez ça. –

+2

Toujours. L'interface générique IEnumerable <> hérite de IDisposable. –

1

Comme nous l'avons souligné dans une autre réponse, il est une méthode MoveNext(), et vous avez accès à tous pour enumerables via l'interface IEnumerator<T> renvoyée par un appel à IEnumerable<T>.GetEnumerator(). Cependant, travailler avec MoveNext() et Current peut sembler quelque peu «bas niveau».

Si vous préférez une boucle foreach pour traiter votre collection getAlphabet(), vous pouvez écrire une méthode d'extension qui renvoie des éléments de toute dénombrable par paires de deux:

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; 
    } 
} 

Vous souhaitez l'utiliser comme suit:

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

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

Et vous obtiendrez la sortie suivante:

# A, B 
# B, C 

(Notez que si la méthode d'extension ci-dessus renvoie des paires de deux éléments chacune, les paires chevauchent d'un seul article! Vous obtenez essentiellement chaque élément ainsi qu'un aperçu. Si vous souhaitez que la méthode d'extension renvoie le dernier élément, vous pouvez l'adapter en adaptant la méthode de tamponnage utilisée.)

0

Comme nous l'avons déjà mentionné, une machine d'état est parfaitement adaptée à ces cas. Il correspond également à la façon dont nous pensons au problème, donc la lisibilité est excellente. Je ne suis pas sûr exactement ce que vous voulez faire avec le code, donc l'exemple ci-dessous renvoie le caractère suivant quand il le rencontre. La machine d'état s'adapte bien aux tâches complexes et peut être modélisée et vérifiée manuellement sur papier.

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

Votre code retournera 'B' à chaque fois, parce que vous appelez getAlphabet(), qui retourne une nouvelle IEnumerable à chaque fois. En fonction de ce que vous essayez de faire, je suggérerais probablement d'utiliser l'itération basée sur l'index au lieu d'un énumérateur. Si vous utilisiez MoveNext pour obtenir l'élément suivant, vous risqueriez d'endommager votre boucle, donc l'utilisation d'une récupération basée sur l'index fonctionnerait plus proprement avec beaucoup moins de frais généraux.

0

Si vous utilisez .NET 4.0 alors ce que vous essayez d'atteindre est très 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 vous en utilisant rien de moins que .NET 4.0, il est encore très facile à define your own Zip function et classe Tuple .