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

C#.NetLinq

C# Problem Overview


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();

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

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

Solution 3 - C#

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

Solution 4 - C#

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.

Solution 5 - C#

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

Solution 6 - C#

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.

Solution 7 - C#

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

Solution 8 - C#

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.

Solution 9 - C#

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

Solution 10 - C#

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

Solution 11 - C#

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

Solution 12 - C#

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?

Solution 13 - C#

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

Solution 14 - C#

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

Solution 15 - C#

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

Solution 16 - C#

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.

Solution 17 - C#

You could write:

var list = xyz.Select(x=>x.Id).ToList();
list.RemoveAt(list.Count - 1);

Solution 18 - C#

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

Solution 19 - C#

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

Solution 20 - C#

Could be:

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

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

Solution 21 - C#

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.

Solution 22 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMikeView Question on Stackoverflow
Solution 1 - C#Justin LessardView Answer on Stackoverflow
Solution 2 - C#DarioView Answer on Stackoverflow
Solution 3 - C#KamareyView Answer on Stackoverflow
Solution 4 - C#JorenView Answer on Stackoverflow
Solution 5 - C#NoldorinView Answer on Stackoverflow
Solution 6 - C#Emiliano RuizView Answer on Stackoverflow
Solution 7 - C#Alex AzaView Answer on Stackoverflow
Solution 8 - C#silasdavisView Answer on Stackoverflow
Solution 9 - C#SmallBizGuyView Answer on Stackoverflow
Solution 10 - C#Matthew LaytonView Answer on Stackoverflow
Solution 11 - C#jr76View Answer on Stackoverflow
Solution 12 - C#Brady MoritzView Answer on Stackoverflow
Solution 13 - C#AdhemarView Answer on Stackoverflow
Solution 14 - C#ddurView Answer on Stackoverflow
Solution 15 - C#TarikView Answer on Stackoverflow
Solution 16 - C#Robert SchmidtView Answer on Stackoverflow
Solution 17 - C#RoJaItView Answer on Stackoverflow
Solution 18 - C#Chibueze OpataView Answer on Stackoverflow
Solution 19 - C#John StevensView Answer on Stackoverflow
Solution 20 - C#Guillermo AresView Answer on Stackoverflow
Solution 21 - C#einordView Answer on Stackoverflow
Solution 22 - C#einordView Answer on Stackoverflow