컬렉션이 주어지면 해당 컬렉션의 마지막 N 요소를 얻는 방법이 있습니까? 프레임 워크에 메소드가 없다면 확장 메소드를 작성하는 가장 좋은 방법은 무엇입니까?
답변
collection.Skip(Math.Max(0, collection.Count() - N));
이 접근 방식은 정렬에 의존하지 않고 항목 순서를 유지하며 여러 LINQ 공급자간에 광범위하게 호환됩니다.
Skip
음수 로 전화하지 않도록주의해야합니다 . Entity Framework와 같은 일부 공급자는 부정적인 인수가 표시되면 ArgumentException을 생성합니다. Math.Max
깔끔하게 피하 라는 부름 .
아래 클래스에는 확장 메소드에 대한 모든 필수 사항이 있습니다. 정적 메소드, 정적 메소드 및 this
키워드 사용입니다 .
public static class MiscExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
}
성능에 대한 간단한 참고 사항 :
호출하면 Count()
특정 데이터 구조가 열거 될 수 있으므로이 접근 방식은 데이터를 두 번 통과시킬 위험이 있습니다. 이것은 실제로 대부분의 열거 가능한 문제가 아닙니다. 실제로 Count()
O (1) 시간 내에 연산 을 평가하기 위해 목록, 배열 및 EF 쿼리에 대한 최적화가 이미 존재합니다 .
그러나 순방향 전용 열거 형을 사용해야하고 두 번의 통과를 피하려면 Lasse V. Karlsen 또는 Mark Byers 와 같은 단일 패스 알고리즘을 고려하십시오 . 이 두 가지 접근 방법은 열거하는 동안 항목을 보유하기 위해 임시 버퍼를 사용하며, 콜렉션의 끝이 발견되면 생성됩니다.
답변
coll.Reverse().Take(N).Reverse().ToList();
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> coll, int N)
{
return coll.Reverse().Take(N).Reverse();
}
업데이트 : clintp의 문제를 해결하려면 : a) 위에서 정의한 TakeLast () 메소드를 사용하면 문제가 해결되지만 추가 메소드없이 실제로 수행하려면 Enumerable.Reverse ()가 될 수 있음을 인식해야합니다. 확장 방법으로 사용되면 다음과 같이 사용할 필요가 없습니다.
List<string> mystring = new List<string>() { "one", "two", "three" };
mystring = Enumerable.Reverse(mystring).Take(2).Reverse().ToList();
답변
참고 : Linq 사용 이라는 질문 제목을 놓쳤 으므로 실제로 Linq를 사용하지 않습니다.
전체 컬렉션의 지연되지 않은 복사본을 캐싱하지 않으려면 연결된 목록을 사용하여 간단한 방법을 작성할 수 있습니다.
다음 방법은 원본 컬렉션에서 찾은 각 값을 연결 목록에 추가하고 연결 목록을 필요한 항목 수로 줄입니다. 컬렉션을 반복하는 동안 링크 된 목록이이 항목 수만큼 잘린 상태로 유지되므로 원본 컬렉션에서 최대 N 개의 항목 복사본 만 유지합니다.
원본 컬렉션의 항목 수를 알 필요가 없으며 여러 번 반복하지 않아도됩니다.
용법:
IEnumerable<int> sequence = Enumerable.Range(1, 10000);
IEnumerable<int> last10 = sequence.TakeLast(10);
...
확장 방법 :
public static class Extensions
{
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> collection,
int n)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
if (n < 0)
throw new ArgumentOutOfRangeException(nameof(n), $"{nameof(n)} must be 0 or greater");
LinkedList<T> temp = new LinkedList<T>();
foreach (var value in collection)
{
temp.AddLast(value);
if (temp.Count > n)
temp.RemoveFirst();
}
return temp;
}
}
답변
열거 가능한 모든 작업에서 작동하지만 O (N) 임시 저장소 만 사용하는 방법은 다음과 같습니다.
public static class TakeLastExtension
{
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int takeCount)
{
if (source == null) { throw new ArgumentNullException("source"); }
if (takeCount < 0) { throw new ArgumentOutOfRangeException("takeCount", "must not be negative"); }
if (takeCount == 0) { yield break; }
T[] result = new T[takeCount];
int i = 0;
int sourceCount = 0;
foreach (T element in source)
{
result[i] = element;
i = (i + 1) % takeCount;
sourceCount++;
}
if (sourceCount < takeCount)
{
takeCount = sourceCount;
i = 0;
}
for (int j = 0; j < takeCount; ++j)
{
yield return result[(i + j) % takeCount];
}
}
}
용법:
List<int> l = new List<int> {4, 6, 3, 6, 2, 5, 7};
List<int> lastElements = l.TakeLast(3).ToList();
N 크기의 링 버퍼를 사용하여 요소를 볼 때 저장하여 이전 요소를 새 요소로 덮어 씁니다. 열거 가능 항목의 끝에 도달하면 링 버퍼에 마지막 N 개의 요소가 포함됩니다.
답변
.NET Core 2.0+는 LINQ 방법을 제공합니다 TakeLast()
.
https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
예 :
Enumerable
.Range(1, 10)
.TakeLast(3) // <--- takes last 3 items
.ToList()
.ForEach(i => System.Console.WriteLine(i))
// outputs:
// 8
// 9
// 10
답변
아무도 언급하지 않았지만 SkipWhile에는 요소의 index 를 사용 하는 메소드가 있습니다 .
public static IEnumerable<T> TakeLastN<T>(this IEnumerable<T> source, int n)
{
if (source == null)
throw new ArgumentNullException("Source cannot be null");
int goldenIndex = source.Count() - n;
return source.SkipWhile((val, index) => index < goldenIndex);
}
//Or if you like them one-liners (in the spirit of the current accepted answer);
//However, this is most likely impractical due to the repeated calculations
collection.SkipWhile((val, index) => index < collection.Count() - N)
이 솔루션이 다른 솔루션보다 유일하게 인식 할 수있는 이점은 IEnumerable을 두 번 통과하는 두 개의 별도 작업을 수행하는 대신보다 강력하고 효율적인 LINQ 쿼리를 만들기 위해 조건자를 추가 할 수 있다는 것입니다.
public static IEnumerable<T> FilterLastN<T>(this IEnumerable<T> source, int n, Predicate<T> pred)
{
int goldenIndex = source.Count() - n;
return source.SkipWhile((val, index) => index < goldenIndex && pred(val));
}
답변
RX의 System.Interactive 어셈블리에서 EnumerableEx.TakeLast를 사용하십시오. @Mark와 같은 O (N) 구현이지만 링 버퍼 구문 대신 대기열을 사용합니다 (버퍼 용량에 도달하면 항목을 대기열에서 제외).
(NB : 이것은 IEnumerable 버전입니다. IObservable 버전은 아닙니다. 두 버전의 구현은 거의 동일합니다)
