programing tip

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

itbloger 2020. 7. 17. 20:57
반응형

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특별한 처리가 필요하지 않습니다.


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을 사용하는 사람들을 위해이 Enumerable.SkipLast(IEnumerable<TSource>, Int32)방법은 .NET Core 2.0에 추가되었습니다.

var sequence = GetSequence();

var allExceptLast = sequence.SkipLast(1);

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


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

확장 프로그램을 출시 할 시간이없는 경우 다음과 같은 방법을 사용하십시오.

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

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많은 요소 를 삭제하기 위해 동일한 버려진 버퍼를 구현 합니다.


열거 형 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);

허용 된 답변에 약간의 변형이 있습니다. 내 취향에 따라 조금 더 간단합니다.

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

.ToList<type>()순서를 정하지 않았고 count를 호출하고 원래했던 것처럼 가져 가십시오.하지만 목록으로 가져 오기 때문에 값 비싼 열거를 두 번 수행 해서는 안됩니다 . 권리?


이 문제에 사용하는 솔루션은 약간 더 정교합니다.

내 util 정적 클래스에는 -items를 -items MarkEnd로 변환 하는 확장 메소드 포함되어 있습니다 . 각 요소는 추가로 표시되는 어느 하나이고, 0 ; 또는 (마지막 3 개 항목에 특히 관심이있는 경우) -3 , -2 또는 -1 마지막 3 개 항목.TEndMarkedItem<T>int

이는, 자신에 유용 할 수있다 예를 들어 간단한에서 목록을 만들 때 foreach마지막 두 제외한 각 요소 다음 쉼표로 -loop을 (같은 관련 단어 다음에 마지막에서 두 번째 항목 " " 또는 " 또는 " "), 마지막 요소 다음에 점이옵니다.

마지막 n 개의 항목 없이 전체 목록을 생성하기 위해 확장 메소드는 ButLast단순히 EndMarkedItem<T>s 동안 반복합니다 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);
                    }
                }
            }    
        }
    }
}

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

나는 이것보다 간결해질 수 있다고 생각하지 않는다 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;
            }
        }
    }
}

편집 : 이 답변 과 기술적으로 동일합니다 .


당신은 쓸 수 있습니다 :

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

이것은 모든 경우를 올바르게 처리하는 일반적인 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();
            }
        }
    }
}

나의 전통적인 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;
    }
}

간단한 방법은 건너 뛰고 싶은 항목 수가 남을 때까지 대기열로 변환하고 대기열에서 꺼내는 것입니다.

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

될 수 있습니다 :

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

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


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.


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.

참고URL : https://stackoverflow.com/questions/1779129/how-to-take-all-but-the-last-element-in-a-sequence-using-linq

반응형