C# 如何使用 LINQ 获取序列中除最后一个元素之外的所有元素?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1779129/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 20:45:09  来源:igfitidea点击:

How to take all but the last element in a sequence using LINQ?

c#.netlinq

提问by Mike

Let's say I have a sequence.

假设我有一个序列。

IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000

Getting the sequence is not cheap and is dynamically generated, and I want to iterate through it once only.

获取序列并不便宜,而且是动态生成的,我只想遍历它一次。

I want to get 0 - 999999 (i.e. everything but the last element)

我想得到 0 - 999999(即除最后一个元素之外的所有内容)

I recognize that I could do something like:

我认识到我可以做这样的事情:

sequence.Take(sequence.Count() - 1);

but that results in two enumerations over the big sequence.

但这会导致对大序列进行两次枚举。

Is there a LINQ construct that lets me do:

是否有一个 LINQ 构造可以让我做:

sequence.TakeAllButTheLastElement();

采纳答案by Dario

I don't know a Linq solution - But you can easily code the algorithm by yourself using generators (yield return).

我不知道 Linq 解决方案 - 但是您可以使用生成器(收益率)轻松地自己编写算法。

public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source) {
    var it = source.GetEnumerator();
    bool hasRemainingItems = false;
    bool isFirst = true;
    T item = default(T);

    do {
        hasRemainingItems = it.MoveNext();
        if (hasRemainingItems) {
            if (!isFirst) yield return item;
            item = it.Current;
            isFirst = false;
        }
    } while (hasRemainingItems);
}

static void Main(string[] args) {
    var Seq = Enumerable.Range(1, 10);

    Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
    Console.WriteLine(string.Join(", ", Seq.TakeAllButLast().Select(x => x.ToString()).ToArray()));
}

Or as a generalized solution discarding the last n items (using a queue like suggested in the comments):

或者作为丢弃最后 n 项的通用解决方案(使用如评论中建议的队列):

public static IEnumerable<T> SkipLastN<T>(this IEnumerable<T> source, int n) {
    var  it = source.GetEnumerator();
    bool hasRemainingItems = false;
    var  cache = new Queue<T>(n + 1);

    do {
        if (hasRemainingItems = it.MoveNext()) {
            cache.Enqueue(it.Current);
            if (cache.Count > n)
                yield return cache.Dequeue();
        }
    } while (hasRemainingItems);
}

static void Main(string[] args) {
    var Seq = Enumerable.Range(1, 4);

    Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
    Console.WriteLine(string.Join(", ", Seq.SkipLastN(3).Select(x => x.ToString()).ToArray()));
}

回答by Noldorin

Nothing in the BCL (or MoreLinq I believe), but you could create your own extension method.

BCL(或我相信的 MoreLinq)中没有任何内容,但您可以创建自己的扩展方法。

public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source)
{
    using (var enumerator = source.GetEnumerator())
        bool first = true;
        T prev;
        while(enumerator.MoveNext())
        {
            if (!first)
                yield return prev;
            first = false;
            prev = enumerator.Current;
        }
    }
}

回答by Kamarey

As an alternative to creating your own method and in a case the elements order is not important, the next will work:

作为创建自己的方法的替代方法,并且在元素顺序不重要的情况下,下一个将起作用:

var result = sequence.Reverse().Skip(1);

回答by Joren

Because I'm not a fan of explicitly using an Enumerator, here's an alternative. Note that the wrapper methods are needed to let invalid arguments throw early, rather than deferring the checks until the sequence is actually enumerated.

因为我不喜欢明确使用 an Enumerator,所以这里有一个替代方案。请注意,需要包装器方法来让无效参数提前抛出,而不是将检查推迟到实际枚举序列。

public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source)
{
    if (source == null)
        throw new ArgumentNullException("source");

    return InternalDropLast(source);
}

private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source)
{
    T buffer = default(T);
    bool buffered = false;

    foreach (T x in source)
    {
        if (buffered)
            yield return buffer;

        buffer = x;
        buffered = true;
    }
}

As per Eric Lippert's suggestion, it easily generalizes to n items:

根据 Eric Lippert 的建议,它很容易推广到 n 项:

public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n)
{
    if (source == null)
        throw new ArgumentNullException("source");

    if (n < 0)
        throw new ArgumentOutOfRangeException("n", 
            "Argument n should be non-negative.");

    return InternalDropLast(source, n);
}

private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
    Queue<T> buffer = new Queue<T>(n + 1);

    foreach (T x in source)
    {
        buffer.Enqueue(x);

        if (buffer.Count == n + 1)
            yield return buffer.Dequeue();
    }
}

Where I now buffer beforeyielding instead of after yielding, so that the n == 0case does not need special handling.

我现在屈服之前而不是在屈服之后进行缓冲,这样n == 0情况就不需要特殊处理了。

回答by Brady Moritz

Why not just .ToList<type>()on the sequence, then call count and take like you did originally..but since it's been pulled into a list, it shouldnt do an expensive enumeration twice. Right?

为什么不只是.ToList<type>()在序列上,然后像最初那样调用 count 和 take ..但是由于它已被拉入列表,它不应该进行两次昂贵的枚举。对?

回答by silasdavis

A slight expansion on Joren's elegant solution:

对 Joren 的优雅解决方案稍作扩展:

public static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right)
{
    int i = 0;
    var buffer = new Queue<T>(right + 1);

    foreach (T x in source)
    {
        if (i >= left) // Read past left many elements at the start
        {
            buffer.Enqueue(x);
            if (buffer.Count > right) // Build a buffer to drop right many elements at the end
                yield return buffer.Dequeue();    
        } 
        else i++;
    }
}
public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1)
{
    return source.Shrink(0, n);
}
public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1)
{
    return source.Shrink(n, 0);
}

Where shrink implements a simple count forward to drop the first leftmany elements and the same discarded buffer to drop the last rightmany elements.

其中shrink 实现一个简单的向前计数以删除前left许多元素,并实现相同的丢弃缓冲区以删除最后right许多元素。

回答by Alex Aza

It would be helpful if .NET Framework was shipped with extension method like this.

如果 .NET Framework 附带这样的扩展方法,那将会很有帮助。

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
    var enumerator = source.GetEnumerator();
    var queue = new Queue<T>(count + 1);

    while (true)
    {
        if (!enumerator.MoveNext())
            break;
        queue.Enqueue(enumerator.Current);
        if (queue.Count > count)
            yield return queue.Dequeue();
    }
}

回答by einord

I would probably do something like this:

我可能会做这样的事情:

sequence.Where(x => x != sequence.LastOrDefault())

This is one iteration with a check that it isn't the last one for each time though.

这是一次迭代,但检查它不是每次的最后一次。

回答by Guillermo Ares

Could be:

可能:

var allBuLast = sequence.TakeWhile(e => e != sequence.Last());

I guess it should be like de "Where" but preserving the order(?).

我想它应该像 de "Where" 但保留顺序(?)。

回答by SmallBizGuy

if you don't have time to roll out your own extension, here's a quicker way:

如果您没有时间推出自己的扩展,这里有一个更快的方法:

var next = sequence.First();
sequence.Skip(1)
    .Select(s => 
    { 
        var selected = next;
        next = s;
        return selected;
    });