Как взять все, кроме последнего элемента в последовательности, используя LINQ?

Скажем, у меня есть последовательность.

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

Получение последовательности не дешево и динамически сгенерировано, и я хочу пропустить ее только один раз.

Я хочу получить 0 - 999999 (т.е. все, кроме последнего элемента)

Я понимаю, что могу сделать что-то вроде:

sequence.Take(sequence.Count() - 1);

но это приводит к двум перечислениям над большой последовательностью.

Есть ли конструкция LINQ, которая позволяет мне делать:

sequence.TakeAllButTheLastElement();

Ответ 1

Я не знаю решения Linq. Но вы можете легко закодировать алгоритм самостоятельно, используя генераторы (return 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()));
}

Или как обобщенное решение, отбрасывающее последние 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()));
}

Ответ 2

Как альтернатива созданию собственного метода, и в случае, когда порядок элементов не важен, следующий будет работать:

var result = sequence.Reverse().Skip(1);

Ответ 3

Потому что я не поклонник явного использования 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;
    }
}

Согласно предложению Эрика Липперта, он легко обобщает на 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();
    }
}

Где я сейчас буфера перед урожаем, а не после урожая, так что для случая n == 0 не требуется специальной обработки.

Ответ 4

Ничего в BCL (или MoreLinq, я считаю), но вы могли бы создать свой собственный метод расширения.

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

Ответ 5

Было бы полезно, если бы .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();
    }
}

Ответ 6

если у вас нет времени для развертывания собственного расширения, здесь более быстрый способ:

var next = sequence.First();
sequence.Skip(1)
    .Select(s => 
    { 
        var selected = next;
        next = s;
        return selected;
    });

Ответ 7

Небольшое расширение на элегантном решении Joren:

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

Если shrink реализует простой подсчет вперед, чтобы удалить первые left многие элементы и тот же отброшенный буфер, чтобы удалить последние right многие элементы.

Ответ 8

Небольшое отклонение от принятого ответа, которое (по моим вкусам) немного проще:

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

Ответ 9

Решение, которое я использую для этой проблемы, несколько более подробно.

Мой статический класс util содержит метод расширения MarkEnd, который преобразует элементы T в EndMarkedItem<T> -элементы. Каждый элемент отмечен дополнительным int, который либо 0; или (в случае, если вас особенно интересуют последние 3 пункта) -3, -2 или -1 для последних 3 элементов.

Это может быть полезно само по себе, например. когда вы хотите создать список в простой foreach -loop с запятыми после каждого элемента, кроме последних 2, со вторым до последнего элемента, за которым следует слово соединения (например, "и" или "или" ) и последний элемент, за которым следует точка.

Для генерации всего списка без последних n элементов метод расширения ButLast просто выполняет итерацию по EndMarkedItem<T>, а EndMark == 0.

Если вы не укажете tailLength, будет отмечен только последний элемент (в MarkEnd()) или опущен (в ButLast()).

Как и другие решения, это работает с помощью буферизации.

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

Ответ 10

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

Ответ 11

Если вы можете перечислить Count или Length, что в большинстве случаев вы можете, просто Take(n - 1)

Пример с массивами

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

Пример с IEnumerable<T>

IEnumerable<int> enu = Enumerable.Range(1, 100);
IEnumerable<int> sub = enu.Take(enu.Count() - 1);

Ответ 12

Я не думаю, что это может быть более кратким, чем это, также обеспечивая Dispose 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;
            }
        }
    }
}

Изменить: технически идентичный этот ответ.

Ответ 13

Почему не просто .ToList<type>() в последовательности, а затем вызовите счетчик и сделайте так, как вы делали изначально... но поскольку он был втянут в список, он не должен делать дорогостоящее перечисление дважды. Правильно?

Ответ 14

Это общее и IMHO элегантное решение, которое будет обрабатывать все случаи правильно:

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

Ответ 15

Вы можете написать:

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

Ответ 17

Мой традиционный подход IEnumerable:

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

Ответ 18

Простой способ - просто преобразовать в очередь и вывести в очередь, пока не останется только количество элементов, которые вы хотите пропустить.

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

Ответ 19

Может быть:

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

Я предполагаю, что это должно быть как де "Где", но сохраняя порядок (?).

Ответ 20

Если скорость является требованием, этот старый школьный путь должен быть самым быстрым, хотя код выглядит не так гладко, как мог бы сделать linq.

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

Это требует, чтобы последовательность была массивом, поскольку она имеет фиксированную длину и индексированные элементы.

Ответ 21

Я бы, наверное, сделал что-то вроде этого:

sequence.Where(x => x != sequence.LastOrDefault())

Это одна итерация с проверкой того, что она не является последней для каждого раза.