Gibt es bei einer Sammlung eine Möglichkeit, die letzten N Elemente dieser Sammlung zu erhalten? Wenn das Framework keine Methode enthält, wie kann man dazu am besten eine Erweiterungsmethode schreiben?
collection.Skip(Math.Max(0, collection.Count() - N));
Dieser Ansatz bewahrt die Artikelreihenfolge ohne Abhängigkeit von einer Sortierung und ist weitgehend kompatibel mit mehreren LINQ-Anbietern.
Es ist wichtig, nicht Skip
mit einer negativen Nummer anzurufen . Einige Anbieter, wie z. B. das Entity Framework, erzeugen eine ArgumentException, wenn ein negatives Argument angezeigt wird. Der Aufruf, Math.Max
dies ordentlich zu vermeiden.
Die folgende Klasse enthält alle wesentlichen Elemente für Erweiterungsmethoden: eine statische Klasse, eine statische Methode und die Verwendung des this
Schlüsselworts.
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));
}
}
Ein kurzer Hinweis zur Leistung:
Da der Aufruf von Count()
eine Aufzählung bestimmter Datenstrukturen verursachen kann, besteht bei diesem Ansatz das Risiko, dass zwei Durchgänge über die Daten verursacht werden. Dies ist bei den meisten Aufzählungen kein wirkliches Problem. Tatsächlich gibt es bereits Optimierungen für Listen, Arrays und sogar EF-Abfragen, um die Count()
Operation in O (1) -Zeit zu bewerten .
Wenn Sie jedoch eine Nur-Vorwärts-Aufzählung verwenden müssen und zwei Durchgänge vermeiden möchten, ziehen Sie einen Ein-Durchlauf-Algorithmus in Betracht, wie ihn Lasse V. Karlsen oder Mark Byers beschreiben. Beide Ansätze verwenden einen temporären Puffer, um Elemente während der Aufzählung zu speichern, die ausgegeben werden, sobald das Ende der Sammlung gefunden ist.
List
s undLinkedList
s ist James 'Lösung tendenziell schneller, wenn auch nicht um eine Größenordnung. Wenn die IEnumerable berechnet wird (z. B. über Enumerable.Range), dauert James 'Lösung länger. Ich kann mir keine Möglichkeit vorstellen, einen einzelnen Durchgang zu garantieren, ohne etwas über die Implementierung zu wissen oder Werte in eine andere Datenstruktur zu kopieren.UPDATE: Um das Problem von clintp zu lösen: a) Die Verwendung der oben definierten TakeLast () -Methode löst das Problem. Wenn Sie jedoch wirklich ohne die zusätzliche Methode auskommen möchten, müssen Sie dies nur erkennen, während Enumerable.Reverse () dies kann Wenn Sie es als Erweiterungsmethode verwenden, müssen Sie es nicht folgendermaßen verwenden:
quelle
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
Ich erhalte einen Compilerfehler, weil .Reverse () void zurückgibt und der Compiler diese Methode anstelle der Linq-Methode wählt, die eine IEnumerable zurückgibt. Vorschläge?N
Datensätzen nicht um die Bestellung kümmern , können Sie die zweite überspringenReverse
.Hinweis : Ich habe Ihren Fragentitel mit der Aufschrift "Verwenden von Linq" verpasst , daher wird in meiner Antwort "Linq" nicht verwendet.
Wenn Sie vermeiden möchten, dass eine nicht verzögerte Kopie der gesamten Sammlung zwischengespeichert wird, können Sie eine einfache Methode schreiben, die dies mithilfe einer verknüpften Liste tut.
Mit der folgenden Methode wird jeder in der ursprünglichen Sammlung gefundene Wert zu einer verknüpften Liste hinzugefügt und die verknüpfte Liste auf die Anzahl der erforderlichen Elemente reduziert. Da die verknüpfte Liste während des gesamten Durchlaufens der Sammlung die ganze Zeit über auf diese Anzahl von Elementen gekürzt bleibt, wird nur eine Kopie von höchstens N Elementen aus der ursprünglichen Sammlung beibehalten.
Es ist nicht erforderlich, dass Sie die Anzahl der Elemente in der Originalsammlung kennen oder mehrmals durchlaufen.
Verwendungszweck:
Verlängerungsmethode:
quelle
Hier ist eine Methode, die für alle Aufzählungen funktioniert, jedoch nur temporären O (N) -Speicher verwendet:
Verwendungszweck:
Es verwendet einen Ringpuffer der Größe N, um die Elemente so zu speichern, wie sie angezeigt werden, und überschreibt alte Elemente mit neuen. Wenn das Ende der Aufzählung erreicht ist, enthält der Ringpuffer die letzten N Elemente.
quelle
n
..NET Core 2.0+ bietet die LINQ-Methode
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
Beispiel :
quelle
netcoreapp1.x
) verfügbar, sondern nur für v2.0 und v2.1 von dotnetcore (netcoreapp2.x
). Es ist möglich, dass Sie auf das gesamte Framework (z. B.net472
) abzielen, das ebenfalls nicht unterstützt wird. (.net-Standardbibliotheken können von allen oben genannten verwendet werden, stellen jedoch möglicherweise nur bestimmte APIs bereit, die für ein Zielframework spezifisch sind. siehe docs.microsoft.com/en-us/dotnet/standard/frameworks )Ich bin überrascht, dass niemand es erwähnt hat, aber SkipWhile hat eine Methode, die den Index des Elements verwendet .
Der einzige wahrnehmbare Vorteil, den diese Lösung gegenüber anderen bietet, besteht darin, dass Sie die Option haben können, ein Prädikat hinzuzufügen, um eine leistungsfähigere und effizientere LINQ-Abfrage zu erstellen, anstatt zwei separate Operationen durchzuführen, die die IEnumerable zweimal durchlaufen.
quelle
Verwenden Sie EnumerableEx.TakeLast in der System.Interactive-Assembly von RX. Es ist eine O (N) -Implementierung wie die von @ Mark, verwendet jedoch eine Warteschlange anstelle eines Ringpufferkonstrukts (und entfernt Elemente, wenn die Pufferkapazität erreicht ist).
(NB: Dies ist die IEnumerable-Version - nicht die IObservable-Version, obwohl die Implementierung der beiden ziemlich identisch ist.)
quelle
Queue<T>
mit einem Ringpuffer implementiert ?Wenn Sie es mit einer Sammlung mit einem Schlüssel zu tun haben (z. B. Einträge aus einer Datenbank), wäre eine schnelle (dh schnellere als die ausgewählte Antwort) Lösung
quelle
Wenn es Ihnen nichts ausmacht, als Teil der Monade in Rx einzutauchen, können Sie Folgendes verwenden
TakeLast
:quelle
Wenn die Verwendung einer Bibliothek eines Drittanbieters eine Option ist, definiert MoreLinq,
TakeLast()
welche genau dies tut.quelle
Ich habe versucht, Effizienz und Einfachheit zu kombinieren und am Ende Folgendes zu erreichen:
Informationen zur Leistung: Wird in C #
Queue<T>
mithilfe eines Ringpuffers implementiert, sodass in jeder Schleife keine Objektinstanziierung durchgeführt wird (nur wenn die Warteschlange wächst). Ich habe keine Warteschlangenkapazität festgelegt (mit einem dedizierten Konstruktor), da diese Erweiterung möglicherweise von jemandem aufgerufen wirdcount = int.MaxValue
. Für zusätzliche Leistung können Sie überprüfen, ob die Quelle implementiert ist,IList<T>
und wenn ja, die letzten Werte mithilfe von Array-Indizes direkt extrahieren.quelle
Es ist ein wenig ineffizient, das letzte N einer Sammlung mit LINQ zu nehmen, da alle oben genannten Lösungen eine Iteration über die Sammlung erfordern.
TakeLast(int n)
inSystem.Interactive
hat auch dieses problem.Wenn Sie eine Liste haben, können Sie sie effizienter mit der folgenden Methode aufteilen
mit
und einige Testfälle
quelle
Ich weiß, dass es zu spät ist, diese Frage zu beantworten. Wenn Sie jedoch mit einer Sammlung vom Typ IList <> arbeiten und sich nicht um eine Reihenfolge der zurückgegebenen Sammlung kümmern, funktioniert diese Methode schneller. Ich habe Mark Byers Antwort verwendet und ein paar Änderungen vorgenommen. Die Methode TakeLast lautet nun:
Zum Test habe ich die Mark Byers-Methode und kbrimington's andswer verwendet . Dies ist Test:
Und hier sind die Ergebnisse für die Aufnahme von 10 Elementen:
und für die Aufnahme von 1000001 Elementen sind die Ergebnisse:
quelle
Hier ist meine Lösung:
Der Code ist etwas klobig, aber als wiederverwendbare Drop-In-Komponente sollte er in den meisten Szenarien so gut wie möglich funktionieren und den Code, der ihn verwendet, schön und präzise halten. :-)
My
TakeLast
for non-IList`1
basiert auf dem gleichen Ringpuffer-Algorithmus wie in den Antworten von @Mark Byers und @MackieChan weiter oben. Es ist interessant, wie ähnlich sie sind - ich habe meine völlig unabhängig geschrieben. Ich denke, es gibt wirklich nur einen Weg, einen Ringpuffer richtig zu machen. :-)Wenn man sich die Antwort von @ kbrimington ansieht, könnte eine zusätzliche Prüfung hinzugefügt werden, um
IQuerable<T>
auf den Ansatz zurückzugreifen, der mit Entity Framework gut funktioniert - vorausgesetzt, das, was ich zu diesem Zeitpunkt habe, nicht.quelle
Unter dem realen Beispiel, wie die letzten 3 Elemente aus einer Sammlung (Array) entnommen werden:
quelle
Verwenden dieser Methode, um alle Bereiche ohne Fehler abzurufen
quelle
Wenig andere Implementierung bei Verwendung von Ringpuffer. Die Benchmarks zeigen, dass die Methode etwa doppelt so schnell ist wie die mit Queue (Implementierung von TakeLast in System.Linq ), jedoch nicht ohne Kosten - sie benötigt einen Puffer, der mit der angeforderten Anzahl von Elementen mitwächst , selbst wenn Sie eine haben kleine Sammlung können Sie große Speicherzuordnung erhalten.
quelle