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


131

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

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

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

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

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

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

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

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

sequence.TakeAllButTheLastElement();

3
O (2n) 시간 또는 O (count) 공간 효율 알고리즘 중 하나를 선택해야합니다. 여기서 후자는 또한 내부 배열에서 항목을 이동해야합니다.
다이 캄

1
다리오, 그렇지 않은 사람에게 설명 해주시겠습니까?
alexn

이 중복 질문도 참조하십시오. stackoverflow.com/q/4166493/240733
stakx-더 이상

컬렉션을 List로 변환 한 다음을 호출하여 캐싱했습니다 sequenceList.RemoveAt(sequence.Count - 1);. 필자의 경우 모든 LINQ 조작 후에 배열 또는 IReadOnlyCollection어쨌든 변환해야하기 때문에 허용 됩니다. 캐싱을 고려하지 않은 유스 케이스가 무엇인지 궁금합니다. 내가 볼 수 있듯이 승인 된 답변조차도 일부 캐싱을 수행하므로 간단한 변환 List은 훨씬 쉽고 짧습니다.
Pavels Ahmadulins

답변:


64

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

7
이제 최종 n을 제외하고 모두 일반화 할 수 있습니까?
Eric Lippert

4
좋은. 하나의 작은 오류; 대기열 크기는 대기열의 최대 크기이므로 n + 1로 초기화해야합니다.
Eric Lippert

ReSharper는 코드를 이해하지 못하지만 ( 조건식에 할당 ) +1과 비슷합니다.
Грозный

44

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

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

49
이를 위해서는 전체 시퀀스를 저장하기에 충분한 메모리가 필요하지만 물론 전체 시퀀스를 두 번 반복하고 한 번은 역순으로 시퀀스를 작성하고 한 번은 반복합니다. 슬라이스 방법에 상관없이 Count 솔루션보다 훨씬 나쁩니다. 속도가 느리고 메모리가 훨씬 많이 걸립니다.
Eric Lippert

'Reverse'방법이 어떻게 작동하는지 모르겠으므로 사용하는 메모리 양에 대해 잘 모르겠습니다. 그러나 나는 두 가지 반복에 동의합니다. 이 방법은 대규모 컬렉션이나 성능이 중요한 경우에는 사용하지 않아야합니다.
Kamarey

5
음, 어떻게 리버스 구현하겠습니까? 전체 시퀀스를 저장하지 않고 일반적인 방법을 알아낼 수 있습니까 ?
Eric Lippert

2
나는 그것을 좋아하고 시퀀스를 두 번 생성하지 않는 기준을 충족시킵니다.
Amy B

12
그리고 추가로 당신은 또한 그대로 유지 다시 전체 순서를 반대로해야합니다 equence.Reverse().Skip(1).Reverse()좋은 솔루션 없습니다
shashwat가

42

나는 명시 적으로 사용하는 팬이 아니기 때문에 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특별한 처리가 필요하지 않습니다.


첫 번째 예에서, buffered=false를 할당하기 전에 else 절에서 설정 하는 것이 약간 더 빠를 것입니다 buffer. 어쨌든 조건을 이미 확인하고 있지만 buffered루프를 통해 매번 중복 설정을 피할 수 있습니다.
James

누군가가 선택한 답변과이 답변의 장단점을 말해 줄 수 있습니까?
Sinjai

입력 검사가없는 별도의 방법으로 구현하면 어떤 이점이 있습니까? 또한 단일 구현을 삭제하고 다중 구현에 기본값을 지정합니다.
jpmc26

@ jpmc26 별도의 메소드로 검사하면 실제로 호출하는 즉시 유효성 검사를받습니다 DropLast. 그렇지 않으면 유효성 검사가 실제로 (에 첫 번째 호출에 순서를 열거하는 경우에만 발생하는 MoveNext결과에 IEnumerator). 생성 IEnumerable과 실제로 열거 사이에 임의의 양의 코드가있을 수있을 때 디버깅하는 것은 재미있는 일이 아닙니다 . 요즘에는 InternalDropLast의 내부 함수로 작성 DropLast하지만 9 년 전에이 코드를 작성할 때 해당 기능은 C #에 존재하지 않았습니다.
Joren

28

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

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


2
이것은 MoreLinq
Leperkawn

SkipLast의 경우 +1 최근에 .NET Framework에서 왔기 때문에 이것을 알지 못했지만 거기에 추가하지 않아도됩니다.
PRMan

12

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

이 코드는 아마되어야합니다 ... 작동하지 않습니다 if (!first)당겨 first = false은 if 밖으로.
Caleb

이 코드는 잘 보입니다. 논리는 ' prev첫 번째 반복에서 초기화되지 않은 것을 반환하고 그 후에 영원히 반복됩니다'.
Phil Miller

코드에 "컴파일 시간"오류가있는 것 같습니다. 수정 하시겠습니까? 그러나 예, 우리는 한 번 반복하고 마지막 항목을 제외한 모든 항목을 반환하는 익스텐더를 작성할 수 있습니다.
Manish Basantani

@ Caleb : 당신은 절대적으로 맞습니다-나는 이것을 진짜 서두에 썼습니다! 지금 수정했습니다. @ Amby : 음, 사용중인 컴파일러가 확실하지 않습니다. 나는 그런 종류의 것이 없었다. : P
Noldorin

@RobertSchmidt 죄송합니다. 나는 using지금 진술을 추가했다 .
Noldorin

7

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

3

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


3

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

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

2

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

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

2

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

문제는 IEnumerables에 관한 것이며 OP는 피하려고하는 솔루션입니다. 코드가 성능에 영향을 미칩니다.
nawfal

1

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


1

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

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

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

1

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

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



0

당신은 쓸 수 있습니다 :

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

0

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

-1

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

SkipLastEnumerable은 전통적이지만 깨졌습니다. 반환하는 첫 번째 요소는 "모델"에 1 개의 요소가 있더라도 항상 정의되지 않은 U입니다. 후자의 경우 빈 결과가 예상됩니다.
Robert Schmidt

또한 IEnumerator <T>는 IDisposable입니다.
Robert Schmidt

참 / 알림. 감사합니다.
Chibueze Opata

-1

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

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

거기에 가져가 항목 알려진 수를 가지고하는 데 사용됩니다. 그리고 충분히 큰 열거 가능한 대기열이 끔찍합니다.
Sinatr

-2

될 수 있습니다 :

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

나는 그것이 "Where"와 같아야하지만 순서를 유지해야한다고 생각합니다 (?).


3
그것은 매우 비효율적 인 방법입니다. sequence.Last ()를 평가하려면 시퀀스의 각 요소에 대해 전체 시퀀스를 순회해야합니다. O (n ^ 2) 효율.
Mike

네 말이 맞아 이 XD를 작성할 때 무슨 생각을했는지 모르겠습니다. 어쨌든 Last ()가 전체 시퀀스를 통과 할 것입니까? IEnumerable (예 : Array)의 일부 구현에서는 O (1)이어야합니다. List 구현을 확인하지는 않았지만 마지막 요소부터 시작하여 "역방향"반복자를 가질 수 있으며 O (1)도 필요합니다. 또한 최소한 기술적으로 말하면 O (n) = O (2n)임을 고려해야합니다. 따라서이 절차가 앱 성능을 절대적으로 비판하지 않으면 훨씬 명확한 순서를 고수합니다 .Take (sequence.Count ()-1).
Guillermo Ares

@ mate와 동의하지 않기 때문에 sequence.Last ()는 O (1)이므로 전체 시퀀스를 통과 할 필요가 없습니다. stackoverflow.com/a/1377895/812598
GoRoS

1
@GoRoS, 시퀀스가 ​​ICollection / IList를 구현하거나 Array 인 경우에만 O (1)입니다. 다른 모든 시퀀스는 O (N)입니다. 내 질문에, 나는 O (1) 소스 중 하나라고 생각하지 않습니다
Mike

3
시퀀스에는이 조건을 만족하는 여러 항목이있을 수 있습니다. e == sequence.Last (), 예를 들어 new [] {1, 1, 1, 1}
Sergey Shandar

-2

속도가 요구 사항이라면 linq가 만들 수있는 코드처럼 매끄럽지 않더라도이 구식 방법이 가장 빠릅니다.

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

고정 길이와 인덱스 된 항목이 있으므로 시퀀스가 ​​배열이어야합니다.


2
인덱스를 통한 요소 액세스를 허용하지 않는 IEnumerable을 처리하고 있습니다. 솔루션이 작동하지 않습니다. 올바르게 수행한다고 가정하면 길이를 결정하기 위해 시퀀스를 순회하고 길이 n-1의 배열을 할당하고 모든 요소를 ​​복사해야합니다. -1. 2n-1 작업 및 (2n-1) * (4 또는 8 바이트) 메모리. 빠르지도 않습니다.
Tarik

-4

아마 다음과 같이 할 것입니다 :

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

이것은 매번 마지막이 아닌지 확인하는 반복입니다.


3
좋지 않은 두 가지 이유. 1) .LastOrDefault ()는 전체 시퀀스를 반복해야하며 시퀀스의 각 요소 (.Where ()에서)에 대해 호출됩니다. 2) 시퀀스가 ​​[1,2,1,2,1,2]이고 기법을 사용한 경우 [1,1,1]이 남습니다.
Mike
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.