Nehmen wir an, ich habe eine Sequenz.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Das Abrufen der Sequenz ist nicht billig und wird dynamisch generiert, und ich möchte sie nur einmal durchlaufen.
Ich möchte 0 - 999999 erhalten (dh alles außer dem letzten Element)
Ich erkenne, dass ich so etwas tun könnte:
sequence.Take(sequence.Count() - 1);
Dies führt jedoch zu zwei Aufzählungen über die große Sequenz.
Gibt es ein LINQ-Konstrukt, mit dem ich Folgendes tun kann:
sequence.TakeAllButTheLastElement();
sequenceList.RemoveAt(sequence.Count - 1);
. In meinem Fall ist es akzeptabel, weil ich es nach allen LINQ-Manipulationen in ein Array oderIReadOnlyCollection
sowieso konvertieren muss. Ich frage mich, was ist Ihr Anwendungsfall, bei dem Sie nicht einmal über Caching nachdenken? Wie ich sehen kann, führt selbst eine genehmigte Antwort ein Caching durch, so dass eine einfache KonvertierungList
meiner Meinung nach viel einfacher und kürzer ist.Antworten:
Ich kenne keine Linq-Lösung - aber Sie können den Algorithmus einfach selbst mit Generatoren codieren (Yield Return).
Oder als allgemeine Lösung, bei der die letzten n Elemente verworfen werden (unter Verwendung einer Warteschlange, wie in den Kommentaren vorgeschlagen):
quelle
Als Alternative zum Erstellen einer eigenen Methode und in einem Fall, in dem die Reihenfolge der Elemente nicht wichtig ist, funktioniert die nächste:
quelle
equence.Reverse().Skip(1).Reverse()
Da ich kein Fan von expliziter Verwendung von bin
Enumerator
, ist hier eine Alternative. Beachten Sie, dass die Wrapper-Methoden erforderlich sind, damit ungültige Argumente frühzeitig ausgelöst werden, anstatt die Überprüfungen zu verschieben, bis die Sequenz tatsächlich aufgelistet ist.Gemäß dem Vorschlag von Eric Lippert lässt es sich leicht auf n Elemente verallgemeinern:
Wo ich jetzt vor dem Nachgeben statt nach dem Nachgeben puffere, so dass der
n == 0
Fall keine besondere Behandlung benötigt.quelle
buffered=false
vor dem Zuweisen eine else-Klausel festzulegenbuffer
. Die Bedingung wird ohnehin bereits überprüft, dies würde jedoch vermeiden, dassbuffered
jedes Mal durch die Schleife redundant eingestellt wird.DropLast
. Andernfalls erfolgt die Validierung nur, wenn Sie die Sequenz tatsächlich aufzählen (beim ersten Aufruf vonMoveNext
auf das ErgebnisIEnumerator
). Es macht keinen Spaß, Fehler zu beheben, wenn zwischen dem GenerierenIEnumerable
und dem tatsächlichen Auflisten eine beliebige Menge an Code vorhanden sein könnte . Heutzutage würde ichInternalDropLast
als innere Funktion von schreibenDropLast
, aber diese Funktionalität gab es in C # nicht, als ich diesen Code vor 9 Jahren schrieb.Die
Enumerable.SkipLast(IEnumerable<TSource>, Int32)
Methode wurde in .NET Standard 2.1 hinzugefügt. Es macht genau das, was Sie wollen.Von https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
quelle
Nichts in der BCL (oder MoreLinq, glaube ich), aber Sie könnten Ihre eigene Erweiterungsmethode erstellen.
quelle
if (!first)
undfirst = false
aus dem if herausziehen .prev
in der ersten Iteration zurückzugeben und danach für immer zu schleifen".using
jetzt eine Erklärung hinzugefügt .Es wäre hilfreich, wenn .NET Framework mit einer solchen Erweiterungsmethode ausgeliefert würde.
quelle
Eine leichte Erweiterung von Jorens eleganter Lösung:
Wobei Shrink eine einfache Vorwärtszählung implementiert, um die ersten
left
vielen Elemente zu löschen, und denselben verworfenen Puffer, um die letztenright
vielen Elemente zu löschen .quelle
Wenn Sie keine Zeit haben, Ihre eigene Erweiterung einzuführen, gehen Sie wie folgt vor:
quelle
Eine kleine Abweichung von der akzeptierten Antwort, die (für meinen Geschmack) etwas einfacher ist:
quelle
Wenn Sie das
Count
oderLength
eines Aufzählers erhalten können, was Sie in den meisten Fällen können, dann einfachTake(n - 1)
Beispiel mit Arrays
Beispiel mit
IEnumerable<T>
quelle
Warum nicht einfach
.ToList<type>()
in der Sequenz, dann zählen und nehmen, wie Sie es ursprünglich getan haben? Aber da es in eine Liste aufgenommen wurde, sollte es nicht zweimal eine teure Aufzählung durchführen. Richtig?quelle
Die Lösung, die ich für dieses Problem verwende, ist etwas ausgefeilter.
Meine statische Klasse util enthält eine Erweiterungsmethode,
MarkEnd
die dieT
-items inEndMarkedItem<T>
-items konvertiert. Jedes Element ist mit einem Extra gekennzeichnetint
, das entweder 0 ist . oder (falls man sich besonders für die letzten 3 Elemente interessiert) -3 , -2 oder -1 für die letzten 3 Elemente.Dies kann für sich genommen nützlich sein, z. B. wenn Sie eine Liste in einer einfachen
foreach
Schleife mit Kommas nach jedem Element mit Ausnahme der letzten 2 erstellen möchten , wobei auf das vorletzte Element ein Konjunktionswort folgt (z. B. " und "). oder " oder ") und das letzte Element, gefolgt von einem Punkt.Um die gesamte Liste ohne die letzten n Elemente zu generieren ,
ButLast
iteriert die Erweiterungsmethode einfach über dasEndMarkedItem<T>
s whileEndMark == 0
.Wenn Sie nichts angeben
tailLength
, wird nur das letzte Element markiert (inMarkEnd()
) oder gelöscht (inButLast()
).Wie bei den anderen Lösungen funktioniert dies durch Puffern.
quelle
quelle
Ich denke nicht, dass es prägnanter werden kann - auch um sicherzustellen, dass
IEnumerator<T>
:Bearbeiten: technisch identisch mit dieser Antwort .
quelle
Mit C # 8.0 können Sie dafür Bereiche und Indizes verwenden .
Standardmäßig erfordert C # 8.0 .NET Core 3.0 oder .NET Standard 2.1 (oder höher). Überprüfen Sie diesen Thread für ältere Implementierungen.
quelle
Sie könnten schreiben:
quelle
Dies ist eine allgemeine und meiner Meinung nach elegante Lösung, die alle Fälle korrekt behandelt:
quelle
Mein traditioneller
IEnumerable
Ansatz:quelle
Eine einfache Möglichkeit wäre, einfach in eine Warteschlange zu konvertieren und die Warteschlange zu verlassen, bis nur noch die Anzahl der Elemente übrig ist, die Sie überspringen möchten.
quelle
Könnte sein:
Ich denke, es sollte wie "Wo" sein, aber die Reihenfolge beibehalten (?).
quelle
Wenn Geschwindigkeit eine Voraussetzung ist, sollte dieser Weg der alten Schule der schnellste sein, auch wenn der Code nicht so flüssig aussieht, wie es linq machen könnte.
Dies erfordert, dass die Sequenz ein Array ist, da sie eine feste Länge und indizierte Elemente hat.
quelle
Ich würde wahrscheinlich so etwas machen:
Dies ist eine Iteration mit der Überprüfung, dass es nicht jedes Mal die letzte ist.
quelle