How to take all but the last element in a sequence

2020-01-27 02:58发布

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# .net linq
22条回答
叛逆
2楼-- · 2020-01-27 03:41

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();
    }
}
查看更多
该账号已被封号
3楼-- · 2020-01-27 03:43

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.

查看更多
Deceive 欺骗
4楼-- · 2020-01-27 03:48

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);
查看更多
够拽才男人
5楼-- · 2020-01-27 03:48

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?

查看更多
仙女界的扛把子
6楼-- · 2020-01-27 03:48

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.

查看更多
祖国的老花朵
7楼-- · 2020-01-27 03:49

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.

查看更多
登录 后发表回答