Biorąc pod uwagę kolekcję, czy istnieje sposób na uzyskanie ostatnich N elementów tej kolekcji? Jeśli nie ma metody w ramach, jaki byłby najlepszy sposób na napisanie metody rozszerzenia, aby to zrobić?
collection.Skip(Math.Max(0, collection.Count() - N));
Takie podejście zachowuje porządek pozycji bez zależności od jakiegokolwiek sortowania i ma szeroką kompatybilność z wieloma dostawcami LINQ.
Ważne jest, aby uważać, aby nie dzwonić Skip
z numerem ujemnym. Niektórzy dostawcy, na przykład Entity Framework, wygenerują ArgumentException, gdy zostaną przedstawione z argumentem ujemnym. Wezwanie do Math.Max
tego starannie unika.
Poniższa klasa zawiera wszystkie niezbędne metody metod rozszerzania, którymi są: klasa statyczna, metoda statyczna i użycie this
słowa kluczowego.
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));
}
}
Krótka uwaga na temat wydajności:
Ponieważ wywołanie Count()
może spowodować wyliczenie niektórych struktur danych, takie podejście może spowodować dwa przejścia danych. To nie jest tak naprawdę problem z większością wyliczeń; w rzeczywistości istnieją już optymalizacje dla list, tablic, a nawet zapytań EF w celu oceny Count()
operacji w czasie O (1).
Jeśli jednak musisz użyć wyliczenia tylko do przodu i chcesz uniknąć wykonywania dwóch przebiegów, rozważ algorytm jednoprzebiegowy, jak opisują Lasse V. Karlsen lub Mark Byers . Oba te podejścia wykorzystują tymczasowy bufor do przechowywania elementów podczas wyliczania, które są uzyskiwane po znalezieniu końca kolekcji.
List
s iLinkedList
s, rozwiązanie Jamesa jest zwykle szybsze, choć nie o rząd wielkości. Jeśli IEnumerable jest obliczany (np. Przez Enumerable.Range), rozwiązanie Jamesa trwa dłużej. Nie mogę wymyślić żadnego sposobu na zagwarantowanie pojedynczego przejścia bez wiedzy o implementacji lub kopiowania wartości do innej struktury danych.AKTUALIZACJA: Aby rozwiązać problem clintp: a) Użycie zdefiniowanej powyżej metody TakeLast () rozwiązuje problem, ale jeśli naprawdę chcesz to zrobić bez dodatkowej metody, musisz to rozpoznać, gdy Enumerable.Reverse () może być użyty jako metoda rozszerzenia, nie musisz go używać w ten sposób:
źródło
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
dostaję błąd kompilatora, ponieważ .Reverse () zwraca void, a kompilator wybiera tę metodę zamiast Linq, która zwraca IEnumerable. Propozycje?N
rekordów, możesz pominąć drugiReverse
.Uwaga : brakowało mi twojego pytania, które brzmiało Korzystanie z Linq , więc moja odpowiedź w rzeczywistości nie używa Linq.
Jeśli chcesz uniknąć buforowania nieludzkiej kopii całej kolekcji, możesz napisać prostą metodę, która robi to za pomocą połączonej listy.
Poniższa metoda doda każdą wartość znalezioną w oryginalnej kolekcji do listy połączonych i przycina listę połączoną do wymaganej liczby elementów. Ponieważ cały czas utrzymuje połączoną listę przyciętą do tej liczby elementów poprzez iterację w kolekcji, zachowa tylko kopię co najwyżej N elementów z oryginalnej kolekcji.
Nie wymaga znajomości liczby elementów w oryginalnej kolekcji ani powtarzania jej więcej niż jeden raz.
Stosowanie:
Metoda przedłużenia:
źródło
Oto metoda, która działa na dowolnym wyliczalnym, ale używa tylko tymczasowego magazynu O (N):
Stosowanie:
Działa przy użyciu bufora pierścieniowego o rozmiarze N do przechowywania elementów, tak jak je widzi, zastępując stare elementy nowymi. Po osiągnięciu końca elementu wymiennego bufor bufora zawiera ostatnie N elementów.
źródło
n
..NET Core 2.0+ zapewnia metodę LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
przykład :
źródło
netcoreapp1.x
), ale tylko dla v2.0 i v2.1 z dotnetcore (netcoreapp2.x
). Możliwe, że celujesz w pełną platformę (np.net472
), Która również nie jest obsługiwana. (Standardowe biblioteki .net mogą być używane przez dowolne z powyższych, ale mogą ujawniać tylko niektóre interfejsy API specyficzne dla docelowego środowiska. zobacz docs.microsoft.com/en-us/dotnet/standard/frameworks )Dziwi mnie, że nikt o tym nie wspominał, ale SkipWhile ma metodę wykorzystującą indeks elementu .
Jedyną zauważalną zaletą tego rozwiązania w porównaniu z innymi jest to, że możesz mieć opcję dodania predykatu, aby utworzyć bardziej wydajne i wydajne zapytanie LINQ, zamiast dwóch osobnych operacji, które dwukrotnie przechodzą przez IEnumerable.
źródło
Użyj EnumerableEx.TakeLast w zestawie System.Interactive RX. Jest to implementacja O (N), taka jak @ Mark, ale wykorzystuje raczej kolejkę niż konstrukcję bufora pierścieniowego (i usuwa z kolejki elementy, gdy osiągnie pojemność bufora).
(Uwaga: jest to wersja IEnumerable - nie wersja IObservable, chociaż implementacja obu jest prawie identyczna)
źródło
Queue<T>
implementowane przy użyciu bufora cyklicznego ?Jeśli masz do czynienia z kolekcją z kluczem (np. Wpisy z bazy danych), szybkim (tj. Szybszym niż wybrana odpowiedź) rozwiązaniem byłoby
źródło
Jeśli nie masz nic przeciwko zanurzeniu się w Rx jako części monady, możesz użyć
TakeLast
:źródło
Jeśli korzystanie z biblioteki innej firmy jest opcją, MoreLinq określa,
TakeLast()
co dokładnie to robi.źródło
Starałem się łączyć wydajność z prostotą i w efekcie:
Informacje o wydajności: w języku C #
Queue<T>
jest implementowany za pomocą bufora cyklicznego, więc nie jest wykonywane tworzenie instancji obiektów w każdej pętli (tylko gdy kolejka rośnie). Nie ustawiłem pojemności kolejki (używając dedykowanego konstruktora), ponieważ ktoś może wywołać to rozszerzeniecount = int.MaxValue
. Aby uzyskać dodatkową wydajność, możesz sprawdzić, czy implementacja źródłowa,IList<T>
a jeśli tak, bezpośrednio wyodrębnij ostatnie wartości za pomocą indeksów tablicowych.źródło
Trochę nieefektywne jest pobranie ostatniego N kolekcji za pomocą LINQ, ponieważ wszystkie powyższe rozwiązania wymagają iteracji w całej kolekcji.
TakeLast(int n)
wSystem.Interactive
ma również ten problem.Jeśli masz listę, bardziej wydajną rzeczą jest wycięcie jej przy użyciu następującej metody
z
i niektóre przypadki testowe
źródło
Wiem, że jest za późno, aby odpowiedzieć na to pytanie. Ale jeśli pracujesz z kolekcją typu IList <> i nie obchodzi Cię kolejność zwracanej kolekcji, ta metoda działa szybciej. Użyłem odpowiedzi Marka Byersa i wprowadziłem małe zmiany. Więc teraz metoda TakeLast to:
Do testu użyłem metody Mark Byers oraz Kbrimington's andswer . To jest test:
A oto wyniki pobrania 10 elementów:
a dla pobrania 1000001 elementów wyniki to:
źródło
Oto moje rozwiązanie:
Kod jest nieco masywny, ale jako składnik wielokrotnego użytku, powinien działać tak dobrze, jak może w większości scenariuszy, i zachowa kod, który go używa. :-)
Mój
TakeLast
za nie-IList`1
opiera się na tym samym algorytmie bufora pierścieniowego, co w odpowiedziach @Mark Byers i @MackieChan w dalszej części. Ciekawe, jak podobne są - napisałem mój całkowicie niezależnie. Sądzę, że naprawdę istnieje tylko jeden sposób na prawidłowe wykonanie bufora pierścieniowego. :-)Patrząc na odpowiedź @ kbrimington, można do tego dodać dodatkowe sprawdzenie,
IQuerable<T>
aby powrócić do podejścia, które działa dobrze z Entity Framework - zakładając, że to, co mam w tym momencie, nie działa.źródło
Poniżej prawdziwy przykład, jak pobrać ostatnie 3 elementy z kolekcji (tablicy):
źródło
Korzystanie z tej metody, aby uzyskać cały zakres bez błędów
źródło
Trochę inna implementacja z użyciem bufora cyklicznego. Testy porównawcze pokazują, że metoda jest około dwa razy szybsza niż metoda korzystająca z kolejki (implementacja TakeLast w System.Linq ), jednak nie bez kosztów - potrzebuje bufora, który rośnie wraz z wymaganą liczbą elementów, nawet jeśli masz mała kolekcja można uzyskać ogromny przydział pamięci.
źródło