如何使用LINQ获取序列中的最后一个元素?

Mik*_*ike 120 .net c# linq

假设我有一个序列.

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

获取序列并不便宜并且是动态生成的,我想只迭代一次.

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

我知道我可以这样做:

sequence.Take(sequence.Count() - 1);
Run Code Online (Sandbox Code Playgroud)

但是这导致了两个大序列的枚举.

是否有LINQ结构可以让我这样做:

sequence.TakeAllButTheLastElement();
Run Code Online (Sandbox Code Playgroud)

Dar*_*rio 57

我不知道Linq解决方案 - 但您可以使用生成器(yield return)轻松编写算法代码.

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()));
}
Run Code Online (Sandbox Code Playgroud)

或者作为丢弃最后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()));
}
Run Code Online (Sandbox Code Playgroud)

  • 现在你可以概括它除了最后的n之外的其他所有吗? (7认同)
  • 尼斯.一个小错误; 队列大小应初始化为n + 1,因为这是队列的最大大小. (4认同)

Kam*_*rey 43

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

var result = sequence.Reverse().Skip(1);
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这需要您有足够的内存来存储整个序列,当然它STILL迭代整个序列两次,一次构建反向序列,一次迭代它.无论你如何切片,这都比Count解决方案更糟糕; 它更慢并且需要更多的内存. (42认同)
  • 此外,你还需要再次反转整个序列以保持它,因为它是`equence.Reverse().跳过(1).反向()`不是一个好的解决方案 (11认同)
  • 那么,你如何实施Reverse?在没有存储整个序列的情况下,你能想出一般**的方法吗? (4认同)
  • 我喜欢它,它确实符合不生成序列两次的标准. (2认同)

Jor*_*ren 42

因为我不是明确使用a的粉丝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;
    }
}
Run Code Online (Sandbox Code Playgroud)

根据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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我现在屈服之前缓冲而不是屈服之后,所以这种n == 0情况不需要特殊处理.


Jus*_*ard 13

对于使用较新版本的.net的用户,该Enumerable.SkipLast(IEnumerable<TSource>, Int32)方法已在.NET Core 2.0中添加。

var sequence = GetSequence();

var allExceptLast = sequence.SkipLast(1);
Run Code Online (Sandbox Code Playgroud)

来源:https : //docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast

  • MoreLinq 中也存在这种情况 (3认同)
  • 跳过最后一个+1。我不知道这一点,因为我最近来自 .NET Framework,而且他们也懒得将其添加到其中。 (2认同)

Nol*_*rin 12

BCL中没有任何东西(或者我相信的更多),但您可以创建自己的扩展方法.

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*Aza 7

如果.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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Emi*_*uiz 7

在 C# 8.0 中,您可以为此使用范围和索引

var allButLast = sequence[..^1];
Run Code Online (Sandbox Code Playgroud)

默认情况下,C# 8.0 需要 .NET Core 3.0 或 .NET Standard 2.1(或更高版本)。检查此线程以用于较旧的实现。