IEnumerable.Skip(1).Take(1).Single() 的替代方案

发布于 2024-09-17 05:07:57 字数 1112 浏览 7 评论 0原文

我遇到了一个看似简单而尴尬的问题,但遇到了困难。我想要的只是 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<> 中的下一个元素。如果有这样的功能,我所有的问题都会得到解决:

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

如果解决方案保持示例的相同结构/布局/功能,那就更好了,但是,我很灵活。我的程序是一个文件解析器,在 200 万行文本中有一些键,例如“money=324”,其中“money”和“324”是 IEnumberable 中的相邻元素,当解析器遇到“money”时,我想要“ 324”。 (谁不呢?:D 抱歉双关语不好。)

I am having a difficult time with a seemingly easy and embarrassing problem. All I want is the next element in an IEnumberable without using Skip(1).Take(1).Single(). This example illustrates the basic problem.

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

Other than being ugly, this example works. But let's say that the IEnumerable contained 2 million elements, then the LINQ statement makes the program execute unbearably slow. What I want is simple. I just want the next element in an IEnumberable<>. All my problems would be solved if there was a function like:

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

It is much preferred if the solution keeps the same structure/layout/functionality of the example however, I am flexible. My program is a file parser, and among the 2 million lines of text are some keys like "money=324" where "money" and "324" are neighbor elements in the IEnumberable and when the parser comes across "money" I want "324". (who doesn't? :D Sorry for bad pun.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

怀念你的温柔 2024-09-24 05:07:57

如果
有一个类似的函数:

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

有一个完全类似的函数。它只是属于 IEnumerator,而不是 IEnumerable

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,而不是 IList?我问这个问题是因为,就好像这并不明显一样,如果您可以通过索引随机访问 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;
        }
    }
}

这不是更容易吗?

All my problems would be solved if
there was a function like:

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

There is a function exactly like that. It just belongs to IEnumerator<T>, not 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;
            }
        }
    }
}

Here's a question for you, though. Is it necessary that this code use an IEnumerable<char>, rather than an IList<char>? I ask because, as if this weren't obvious, the code would be much simpler if you had random access to the items returned by getAlphabet by index (and if someone is tempted to point out that you can do this with ElementAt, please, just get that idea out of your head right now).

I mean, consider what the code would look like in this case:

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

Isn't that much easier?

满意归宿 2024-09-24 05:07:57

我认为你想要这个:

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

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

I reckon you want this:

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

Note that this consumes the next element, just what you want if I read your question right.

春风十里 2024-09-24 05:07:57

正如另一个答案中指出的那样,有一个 MoveNext() 方法,您可以通过 IEnumerator 访问所有可枚举的方法。 code> 调用 IEnumerable.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

请注意,虽然上述扩展方法每个返回两个项目对,但这些对重叠一个项目!您实际上也获得了每个项目如果您希望扩展方法本身返回最后一项,您可以通过调整所使用的缓冲方法来调整它。)

As was pointed out in another answer, there is a MoveNext() method, and you have access to it for all enumerables via the IEnumerator<T> interface returned by a call to IEnumerable<T>.GetEnumerator(). However, working with MoveNext() and Current can feel somewhat "low-level".

If you'd prefer a foreach loop to process your getAlphabet() collection, you could write an extension method that returns elements from any enumerable in pairs of two:

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

You'd use it as follows:

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

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

And you'd get the following output:

#  A, B
#  B, C

(Note that while the above extension method returns pairs of two items each, the pairs overlap by one item! You essentially get each item as well as a look-ahead. If you wanted the extension method to return the last item by itself, you could adapt it by adapting the buffering method used.)

倾`听者〃 2024-09-24 05:07:57

正如前面提到的,状态机非常适合这些情况。
它也符合我们思考问题的方式,因此可读性非常好。
我不确定您到底想用代码做什么,所以下面的示例在遇到下一个字符时返回它。状态机可以很好地适应复杂的任务,并且可以在纸上进行建模和手动检查。

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

As alluded to earlier, a state machine is ideally suited for these cases.
It also matches the way we think about the problem, so readability is excellent.
I'm not sure exactly what you want to do with the code, so the example below returns the next character when it encounters it. The state machine scales well to complex tasks, and can be modeled and hand-checked on paper.

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;
        }
    }
}
地狱即天堂 2024-09-24 05:07:57

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

根据您想要执行的操作,我可能建议使用基于索引的迭代而不是枚举器。如果您使用 MoveNext 来获取下一个元素,则会弄乱循环,因此使用基于索引的检索会更干净地工作,并且开销要少得多。

Your code will return 'B' every time, because you call getAlphabet(), which returns a new IEnumerable each time.

Based on what you're trying to do, I'd probably suggest using index-based iteration instead of an enumerator. If you used MoveNext to get the next element, you'd mess up your loop, so using an index-based retrieval would work more cleanly with much less overhead.

方圜几里 2024-09-24 05:07:57

如果您使用的是 .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 的任何内容,它仍然很容易 定义您自己的Zip 函数 和 Tuple 类。

If you're using .NET 4.0 then what you're trying to achieve is very 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

If you using anything less than .NET 4.0, its still very easy to define your own Zip function and Tuple class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文