2010-08-28 58 views
5

我有一個看似簡單而令人尷尬的難題。我只想要IEnumberable中的下一個元素,而不使用Skip(1).Take(1).Single()。這個例子說明了基本問題。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; 
     } 
    } 
} 

除了醜陋,這個例子的作品。但是讓我們假設IEnumerable包含200萬個元素,那麼LINQ語句會使程序的執行速度變得難以忍受。我想要的很簡單。我只想要IEnumberable中的下一個元素> <>。

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

如果解決方案保持相同的結構/設計/例子的功能但是它更喜歡,我很靈活:如果有類似的功能我所有的問題將得到解決。我的程序是一個文件解析器,在200萬行文本中有一些鍵爲「money = 324」,其中「money」和「324」是IEnumberable中的相鄰元素,當解析器遇到「money」時, 324" 。 (誰不?。d對不起,壞的雙關語)

+0

這聽起來像是FSM的情況。 – Necros 2010-08-28 15:39:48

+2

此代碼將始終檢索序列的第二個元素,即'B',我不認爲這就是你想要的... – 2010-08-28 15:47:22

+0

topvoted答案已經回答了你的問題。但是,爲了將來的參考,而不是'.Take(1).Single()',你可以使用'.First()'做同樣的事情。 – Timwi 2010-08-28 15:55:53

回答

13

我所有的問題將得到解決,如果 有一個功能,如:

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

有一個功能正是這樣。它只屬於IEnumerator<T>,而不是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; 
      } 
     } 
    } 
} 

雖然這是一個問題。此代碼是否需要使用IEnumerable<char>而不是IList<char>?我問,因爲如果這不是顯而易見的話,如果您按索引對getAlphabet返回的項目進行隨機訪問,那麼代碼會簡單得多(如果有人試圖指出您可以使用ElementAt來做到這一點,現在就把你的想法從你的腦海中解救出來)。

我的意思是,考慮代碼是什麼樣子在這種情況下:

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

是不是更容易?

+0

你的第一個答案正是我正在尋找的東西(有時,對某件事物的新觀點正在尋找)。不過,我對你的建議有些困惑。我不想隨機訪問。我希望在這種情況下,他們來信。而且,我可能錯了,是不是getAlphabet()在內存中保存整個內容?我不希望這樣,因爲在我的情況下,getAlphabet()將持有200萬行。 – 2010-08-28 19:14:46

+0

啊,我剛剛意識到可能會有一些混淆。字母表只是我問題的一個例子。在現實中,我不會對字母或字符做任何事情。如果希望我可以提供相關的代碼,但你已經回答了我的問題。 – 2010-08-28 20:13:48

+0

@尼克:不需要進一步澄清。即使在發佈我的建議後不久,我記得你曾說過200萬件物品;所以顯然將內容存儲在內存中對你而言是沒有意義的。儘管如此,如果其他人發現他/她自己面臨類似問題,那麼在通過索引訪問集合中的成員的場景中,實際上可能是一種選擇。 – 2010-08-29 05:43:34

4

我想你想的:

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

注意,這消耗了下一個元素,只是你想要什麼,如果我向右讀你的問題。

+1

真的應該在'GetEnumerator'附近使用'''... – Timwi 2010-08-28 15:53:22

+1

是的,如果你忘記了,主板會着火。 – 2010-08-28 16:00:25

+2

永遠。泛型IEnumerable <>接口從IDisposable繼承。 – 2010-08-28 16:02:26

1

正如在另一個答案指出,有一個MoveNext()方法,你必須通過由調用返回IEnumerable<T>.GetEnumerator()IEnumerator<T>接口訪問它的所有可枚舉。但是,與MoveNext()Current一起工作會感覺有點「低級」。

如果你喜歡一個foreach循環處理您的收藏getAlphabet(),你可以寫一個返回從任何枚舉元素對二的擴展方法:

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

如下你會使用它:

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

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

而且你會得到下面的輸出:

# A, B 
# B, C 

(請注意,雖然上述擴展方法分別返回兩個項目的配對,但配對重疊了一個項目!你基本上可以獲得每件物品以及預見性。如果您希望擴展方法自行返回最後一個項目,則可以通過調整所使用的緩衝方法來調整它。)

0

如前所述,狀態機非常適合這些情況。 它也符合我們思考問題的方式,所以可讀性非常好。 我不確定你想要用代碼做什麼,所以下面的例子在遇到它時返回下一個字符。狀態機可以很好地適應複雜的任務,可以在紙上建模和手動檢查。

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

您的代碼將每次返回'B',因爲你叫getAlphabet(),其中新IEnumerable每次返回。

根據你想要做的,我可能會建議使用基於索引的迭代而不是枚舉器。如果您使用MoveNext來獲取下一個元素,那麼您會搞亂循環,所以使用基於索引的檢索將更加乾淨利落,而且開銷更少。

0

如果你使用.NET 4.0,然後你想達到什麼目的很簡單:

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 

如果你使用任何低於.NET 4.0,它還是很容易define your own Zip function和元組類。