替代IEnumerable <t> .Skip(1).Take(1).Single()</t>

时间:2010-08-28 15:34:08

标签: c# linq foreach ienumerable yield

我遇到了一个看似简单且令人尴尬的困难时期。我想要的只是IEnumberable中的下一个元素而不使用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&lt;&gt;中的下一个元素。如果有如下函数,我的所有问题都将得到解决:

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

如果解决方案保持与示例相同的结构/布局/功能,则更为优选,但我很灵活。我的程序是一个文件解析器,在200万行文本中有一些键,如“money = 324”,其中“money”和“324”是IEnumberable中的邻居元素,当解析器出现“money”时,我想“ 324" 。 (谁没有?:D对不起的双关语。)

6 个答案:

答案 0 :(得分: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;
        }
    }
}

不是那么容易吗?

答案 1 :(得分:4)

我估计你想要这个:

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

请注意,如果我正确地阅读您的问题,这会消耗下一个元素。

答案 2 :(得分:1)

正如在另一个答案中指出的那样,一个MoveNext()方法,并且您可以通过调用返回的IEnumerator<T>接口访问所有可用的枚举。 IEnumerable<T>.GetEnumerator()。但是,使用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

(注意,虽然上面的扩展方法每个都返回两个项目对,但是重叠一个项目!你基本上得到每个项目以及前瞻。如果你想要扩展名方法返回最后一项,你可以通过调整使用的缓冲方法来调整它。)

答案 3 :(得分: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;
        }
    }
}

答案 4 :(得分:0)

您的代码每次都会返回'B',因为您致电getAlphabet(),每次都会返回一个新的IEnumerable

根据您的尝试,我可能建议使用基于索引的迭代而不是枚举器。如果你使用MoveNext获取下一个元素,那么你就会搞乱你的循环,所以使用基于索引的检索可以更加干净地工作,而且开销更少。

答案 5 :(得分: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和Tuple类。