[C#] LINQ를 사용하여 시퀀스에서 마지막 요소를 제외한 모든 요소를 ​​가져 오는 방법은 무엇입니까?

시퀀스가 있다고 가정 해 봅시다.

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

시퀀스를 얻는 것은 저렴하지 않고 동적으로 생성되므로 한 번만 반복하고 싶습니다.

0-999999를 얻고 싶습니다 (즉, 마지막 요소를 제외한 모든 것)

나는 다음과 같은 것을 할 수 있음을 알고 있습니다.

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

그러나 그 결과 큰 시퀀스에 대해 두 가지 열거가 발생합니다.

내가 할 수있는 LINQ 구문이 있습니까?

sequence.TakeAllButTheLastElement();



답변

Linq 솔루션을 모르겠습니다. 그러나 생성기를 사용하여 알고리즘을 쉽게 코딩 할 수 있습니다 (수율 반환).

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


답변

자체 메소드를 작성하는 대신 요소 순서가 중요하지 않은 경우 다음이 작동합니다.

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


답변

나는 명시 적으로 사용하는 팬이 아니기 때문에 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;
    }
}

Eric Lippert의 제안에 따라 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특별한 처리가 필요하지 않습니다.


답변

Enumerable.SkipLast(IEnumerable<TSource>, Int32)방법은 .NET Standard 2.1에 추가되었습니다. 정확히 원하는 것을 수행합니다.

IEnumerable<int> sequence = GetSequenceFromExpensiveSource();

var allExceptLast = sequence.SkipLast(1);

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

소스 컬렉션의 마지막 개수 요소가 생략 된 소스의 요소를 포함하는 새로운 열거 가능한 컬렉션을 반환합니다.


답변

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


답변

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


답변

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

축소는 첫 번째 left많은 요소 를 삭제하기 위해 간단한 카운트 포워드를 구현 하고 마지막 right많은 요소 를 삭제하기 위해 동일한 버림 버퍼를 구현 합니다.