171

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)

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:

sequence.TakeAllButTheLastElement();
Vikas Gupta
  • 4,455
  • 1
  • 20
  • 40
Mike
  • 5,560
  • 12
  • 41
  • 52
  • 3
    You've to choose between an O(2n) time or O(count) space efficiency algorithm, where the latter also needs to move items in an internal array. – Dykam Nov 22 '09 at 16:18
  • 1
    Dario, would you please explain for someone who's not that into big o-notation? – alexn Nov 22 '09 at 16:56
  • See also this duplicate question: http://stackoverflow.com/q/4166493/240733 – stakx - no longer contributing Feb 04 '15 at 08:36
  • 1
    I ended up with caching it by converting collection to List and then calling `sequenceList.RemoveAt(sequence.Count - 1);`. In my case it is acceptable because after all LINQ manipulations I have to convert it to array or `IReadOnlyCollection` anyway. I wonder what is your use case where you do not even consider caching? As I can see even approved answer does some caching so simple converting to `List` is much easier and shorter in my opinion. – Pavels Ahmadulins Aug 11 '16 at 09:36

22 Answers22

96

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://learn.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.

Justin Lessard
  • 10,804
  • 5
  • 49
  • 61
68

I don't know a Linq solution - But you can easily code the algorithm by yourself using generators (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()));
}

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

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()));
}
Dario
  • 48,658
  • 8
  • 97
  • 130
50

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);
Kamarey
  • 10,832
  • 7
  • 57
  • 70
  • 61
    Note that this requires that you have enough memory to store the entire sequence, and of course it STILL iterates the entire sequence twice, once to build the reversed sequence and once to iterate it. Pretty much this is worse than the Count solution no matter how you slice it; it's slower AND takes far more memory. – Eric Lippert Nov 22 '09 at 16:56
  • I don't know how the 'Reverse' method work, so I'm not sure about amount of memory it use. But I agree about two iterations. This method shouldn't be used on large collections or if a performance is important. – Kamarey Nov 22 '09 at 17:07
  • 6
    Well, how would *you* implement Reverse? Can you figure out a way *in general* to do it without storing the entire sequence? – Eric Lippert Nov 22 '09 at 19:00
  • Don't see anything better than O(n) of space. Sure that better to use solution of custom method for this purpose. – Kamarey Nov 22 '09 at 19:32
  • 2
    I like it, and it does meet the criteria of not generating the sequence twice. – Amy B Nov 23 '09 at 01:46
  • 14
    and in addition you will also need to reverse the entire sequence again to keep it as it is `equence.Reverse().Skip(1).Reverse()` not a good solution – shashwat Sep 07 '13 at 14:55
42

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.

Joren
  • 14,472
  • 3
  • 50
  • 54
  • In the first example, it would probably be ever-so-slightly faster to set `buffered=false` in an else clause before assigning `buffer`. The condition is already being checked anyway, but this would avoid redundantly setting `buffered` every time through the loop. – James Aug 23 '17 at 13:19
  • Can someone tell me the pros/cons of this versus the selected answer? – Sinjai Sep 08 '17 at 21:14
  • What is the advantage of having the implementation in a separate method that lacks the input checks? Also, I'd just drop the single implementation and give the multiple implementation a default value. – jpmc26 Aug 31 '18 at 16:31
  • @jpmc26 With the check in a separate method, you actually get the validation the moment you call `DropLast`. Otherwise the validation happens only when you actually enumerate the sequence (on the first call to `MoveNext` on the resulting `IEnumerator`). Not a fun thing to debug when there could be an arbitrary amount of code between generating the `IEnumerable` and actually enumerating it. Nowadays I'd write `InternalDropLast` as an inner function of `DropLast`, but that functionality didn't exist in C# when I wrote this code 9 years ago. – Joren Sep 03 '18 at 07:44
16

With C# 8.0 you can use Ranges and indices for that.

var allButLast = sequence[..^1];

By default C# 8.0 requires .NET Core 3.0 or .NET Standard 2.1 (or above). Check this thread to use with older implementations.

Emiliano Ruiz
  • 412
  • 4
  • 16
12

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

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;
        }
    }
}
Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • This code won't work... should probably be `if (!first)` and pull `first = false` out of the if. – Caleb Nov 22 '09 at 16:17
  • This code looks off. The logic seems to be 'return the uninitialized `prev` in the first iteration, and loop forever after that'. – Phil Miller Nov 22 '09 at 16:18
  • The code seems to have "compile time" error. May be you would like to correct it. But yes, we can write an extender which iterates once and returns all but the last item. – Manish Basantani Nov 22 '09 at 16:19
  • @Caleb: You are absolutely right - I wrote this in a real rush! Fixed now. @Amby: Erm, not sure what compiler you're using. I had nothing of the sort. :P – Noldorin Nov 22 '09 at 16:41
  • @RobertSchmidt Oops, yes. I added a `using` statement now. – Noldorin Nov 07 '17 at 14:08
7

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();
    }
}
Alex Aza
  • 76,499
  • 26
  • 155
  • 134
4

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

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.

Silas Davis
  • 712
  • 1
  • 9
  • 15
3

If you can get the Count or Length of an enumerable, which in most cases you can, then just Take(n - 1)

Example with arrays

int[] arr = new int[] { 1, 2, 3, 4, 5 };
int[] sub = arr.Take(arr.Length - 1).ToArray();

Example with IEnumerable<T>

IEnumerable<int> enu = Enumerable.Range(1, 100);
IEnumerable<int> sub = enu.Take(enu.Count() - 1);
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 1
    The question is about IEnumerables and your solution is what OP is trying to avoid. Your code has performance impact. – nawfal Mar 20 '18 at 19:32
2

A slight variation on the accepted answer, which (for my tastes) is a bit simpler:

    public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
    {
        // for efficiency, handle degenerate n == 0 case separately 
        if (n == 0)
        {
            foreach (var item in enumerable)
                yield return item;
            yield break;
        }

        var queue = new Queue<T>(n);
        foreach (var item in enumerable)
        {
            if (queue.Count == n)
                yield return queue.Dequeue();

            queue.Enqueue(item);
        }
    }
jrr
  • 194
  • 10
1

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?

MAXE
  • 4,978
  • 2
  • 45
  • 61
Brady Moritz
  • 8,624
  • 8
  • 66
  • 100
1

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);
                    }
                }
            }    
        }
    }
}
Adhemar
  • 151
  • 3
1
    public static IEnumerable<T> NoLast<T> (this IEnumerable<T> items) {
        if (items != null) {
            var e = items.GetEnumerator();
            if (e.MoveNext ()) {
                T head = e.Current;
                while (e.MoveNext ()) {
                    yield return head; ;
                    head = e.Current;
                }
            }
        }
    }
ddur
  • 75
  • 3
1

This is a general and IMHO elegant solution that will handle all cases correctly:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        IEnumerable<int> r = Enumerable.Range(1, 20);
        foreach (int i in r.AllButLast(3))
            Console.WriteLine(i);

        Console.ReadKey();
    }
}

public static class LinqExt
{
    public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
    {
        using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
        {
            Queue<T> queue = new Queue<T>(n);

            for (int i = 0; i < n && enumerator.MoveNext(); i++)
                queue.Enqueue(enumerator.Current);

            while (enumerator.MoveNext())
            {
                queue.Enqueue(enumerator.Current);
                yield return queue.Dequeue();
            }
        }
    }
}
Tarik
  • 10,810
  • 2
  • 26
  • 40
1

I don't think it can get more succinct than this - also ensuring to Dispose the IEnumerator<T>:

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
    using (var it = source.GetEnumerator())
    {
        if (it.MoveNext())
        {
            var item = it.Current;
            while (it.MoveNext())
            {
                yield return item;
                item = it.Current;
            }
        }
    }
}

Edit: technically identical to this answer.

Robert Schmidt
  • 699
  • 4
  • 14
-1

You could write:

var list = xyz.Select(x=>x.Id).ToList();
list.RemoveAt(list.Count - 1);
RoJaIt
  • 451
  • 3
  • 10
-1

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;
    }
}
Chibueze Opata
  • 9,856
  • 7
  • 42
  • 65
-3

Could be:

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

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

Guillermo Ares
  • 197
  • 1
  • 12
  • 4
    That's a very inefficient way to do it. To evaluate sequence.Last() it has to traverse the entire sequence, doing so for each element in the sequence. O(n^2) efficiency. – Mike Nov 22 '13 at 21:43
  • You are right. I don't know what I was thinking when I wrote this XD. Anyway, are you sure that Last() will traverse the entire sequence? For some implementations of IEnumerable (such as Array), this should be O(1). I didn't check the List implementation, but it could also have a "reverse" iterator, starting in the last element, which would take O(1) too. Also, you should take into account that O(n) = O(2n), at least technically speaking. Hence, if this procedure is not absolutely critic for your apps performance, I would stick with the much clearer sequence.Take(sequence.Count() - 1).Regards! – Guillermo Ares Dec 20 '13 at 18:03
  • @Mike I don't agree with you mate, sequence.Last() is O(1) so that it doesn't need to traverse the entire sequence. http://stackoverflow.com/a/1377895/812598 – GoRoS Jan 15 '14 at 17:17
  • 1
    @GoRoS, it's only O(1) if the sequence implements ICollection/IList or is an Array. All other sequences are O(N). In my question, I don't assume that one of the O(1) sources – Mike Jan 15 '14 at 20:57
  • @Mike Well yes, you're right, depending on what you implement on sequence. However it seems that it's not O(1) for ICollection/IList as you said, it's just O(1) for IList only http://stackoverflow.com/a/18200099/812598 – GoRoS Jan 16 '14 at 08:14
  • 4
    The sequence may have several items that satisfies this condition e == sequence.Last(), for example new [] { 1, 1, 1, 1 } – Sergey Shandar Aug 15 '15 at 00:03
-3

If speed is a requirement, this old school way should be the fastest, even though the code doesn't look as smooth as linq could make it.

int[] newSequence = int[sequence.Length - 1];
for (int x = 0; x < sequence.Length - 1; x++)
{
    newSequence[x] = sequence[x];
}

This requires that the sequence is an array since it has a fixed length and indexed items.

einord
  • 2,278
  • 2
  • 20
  • 26
  • 2
    You are dealing with an IEnumerable that does not allow access to elements via an index. Your solution does not work. Assuming you do it right, it requires traversing the sequence to determine the length, allocating an array of length n-1, copying all of the elements. - 1. 2n-1 operations and (2n-1) * (4 or 8 bytes) of memory. That's not even fast. – Tarik Nov 08 '17 at 23:35
-3

A simple way would be to just convert to a queue and dequeue until only the number of items you want to skip is left.

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
{
    var queue = new Queue<T>(source);

    while (queue.Count() > n)
    {
        yield return queue.Dequeue();
    }
}
  • There is [Take](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.take) used to take known number of items. And queue for big enough enumerable is awful. – Sinatr Feb 13 '19 at 14:04
-6

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.

einord
  • 2,278
  • 2
  • 20
  • 26
  • 5
    Two reasons that's not a good thing to do. 1) .LastOrDefault() requires iterating the entire sequence, and that's called for each element in the sequence (in the .Where()). 2) If the sequence is [1,2,1,2,1,2] and you used your technique, you'd be left with [1,1,1]. – Mike Sep 05 '12 at 13:45