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

前端 未结 22 1412
南笙
南笙 2020-11-30 02:51

Let\'s say I have a sequence.

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


        
相关标签:
22条回答
  • 2020-11-30 03:14

    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.

    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:

    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 before yielding instead of after yielding, so that the n == 0 case does not need special handling.

    0 讨论(0)
  • 2020-11-30 03:15

    The Enumerable.SkipLast(IEnumerable<TSource>, Int32) method was added in .NET Standard 2.1. It does exactly what you want.

    IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
    
    var allExceptLast = sequence.SkipLast(1);
    

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

    Returns a new enumerable collection that contains the elements from source with the last count elements of the source collection omitted.

    0 讨论(0)
  • 2020-11-30 03:15

    The solution that I use for this problem is slightly more elaborate.

    My util static class contains an extension method MarkEnd which converts the T-items in EndMarkedItem<T>-items. Each element is marked with an extra int, which is either 0; or (in case one is particularly interested in the last 3 items) -3, -2, or -1 for the last 3 items.

    This could be useful on its own, e.g. when you want to create a list in a simple foreach-loop with commas after each element except the last 2, with the second-to-last item followed by a conjunction word (such as “and” or “or”), and the last element followed by a point.

    For generating the entire list without the last n items, the extension method ButLast simply iterates over the EndMarkedItem<T>s while EndMark == 0.

    If you don’t specify tailLength, only the last item is marked (in MarkEnd()) or dropped (in ButLast()).

    Like the other solutions, this works by buffering.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Adhemar.Util.Linq {
    
        public struct EndMarkedItem<T> {
            public T Item { get; private set; }
            public int EndMark { get; private set; }
    
            public EndMarkedItem(T item, int endMark) : this() {
                Item = item;
                EndMark = endMark;
            }
        }
    
        public static class TailEnumerables {
    
            public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts) {
                return ts.ButLast(1);
            }
    
            public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts, int tailLength) {
                return ts.MarkEnd(tailLength).TakeWhile(te => te.EndMark == 0).Select(te => te.Item);
            }
    
            public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts) {
                return ts.MarkEnd(1);
            }
    
            public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts, int tailLength) {
                if (tailLength < 0) {
                    throw new ArgumentOutOfRangeException("tailLength");
                }
                else if (tailLength == 0) {
                    foreach (var t in ts) {
                        yield return new EndMarkedItem<T>(t, 0);
                    }
                }
                else {
                    var buffer = new T[tailLength];
                    var index = -buffer.Length;
                    foreach (var t in ts) {
                        if (index < 0) {
                            buffer[buffer.Length + index] = t;
                            index++;
                        }
                        else {
                            yield return new EndMarkedItem<T>(buffer[index], 0);
                            buffer[index] = t;
                            index++;
                            if (index == buffer.Length) {
                                index = 0;
                            }
                        }
                    }
                    if (index >= 0) {
                        for (var i = index; i < buffer.Length; i++) {
                            yield return new EndMarkedItem<T>(buffer[i], i - buffer.Length - index);
                        }
                        for (var j = 0; j < index; j++) {
                            yield return new EndMarkedItem<T>(buffer[j], j - index);
                        }
                    }
                    else {
                        for (var k = 0; k < buffer.Length + index; k++) {
                            yield return new EndMarkedItem<T>(buffer[k], k - buffer.Length - index);
                        }
                    }
                }    
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 03:15

    My traditional IEnumerable approach:

    /// <summary>
    /// Skips first element of an IEnumerable
    /// </summary>
    /// <typeparam name="U">Enumerable type</typeparam>
    /// <param name="models">The enumerable</param>
    /// <returns>IEnumerable of type skipping first element</returns>
    private IEnumerable<U> SkipFirstEnumerable<U>(IEnumerable<U> models)
    {
        using (var e = models.GetEnumerator())
        {
            if (!e.MoveNext()) return;
            for (;e.MoveNext();) yield return e.Current;
            yield return e.Current;
        }
    }
    
    /// <summary>
    /// Skips last element of an IEnumerable
    /// </summary>
    /// <typeparam name="U">Enumerable type</typeparam>
    /// <param name="models">The enumerable</param>
    /// <returns>IEnumerable of type skipping last element</returns>
    private IEnumerable<U> SkipLastEnumerable<U>(IEnumerable<U> models)
    {
        using (var e = models.GetEnumerator())
        {
            if (!e.MoveNext()) return;
            yield return e.Current;
            for (;e.MoveNext();) yield return e.Current;
        }
    }
    
    0 讨论(0)
  • 2020-11-30 03:19

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

    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-30 03:20

    A slight expansion on Joren's elegant solution:

    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 left many elements and the same discarded buffer to drop the last right many elements.

    0 讨论(0)
提交回复
热议问题