Ich suche nach einem besseren Muster für die Arbeit mit einer Liste von Elementen, die jeweils verarbeitet werden müssen und dann je nach Ergebnis aus der Liste entfernt werden.
Sie können nicht .Remove(element)
innerhalb von verwenden foreach (var element in X)
(weil dies zu einer Collection was modified; enumeration operation may not execute.
Ausnahme führt) ... Sie können auch nicht verwendenfor (int i = 0; i < elements.Count(); i++)
und .RemoveAt(i)
weil es Ihre aktuelle Position in der Sammlung relativ zu stört i
.
Gibt es eine elegante Möglichkeit, dies zu tun?
list.RemoveAll(Function(item) item.Value = somevalue)
.size()
anstelle von.Count
und.remove(i)
anstelle von.removeAt(i)
. Clever - danke!RemoveAll()
dreimal so lange dauert wie die Rückwärtsschleifefor
. Ich bleibe also definitiv bei der Schleife, zumindest in Abschnitten, in denen es darauf ankommt.Eine einfache und unkomplizierte Lösung:
Verwenden Sie eine Standard-for-Schleife, die in Ihrer Sammlung rückwärts ausgeführt wird, und
RemoveAt(i)
entfernen Sie Elemente.quelle
Die umgekehrte Iteration sollte als Erstes in den Sinn kommen, wenn Sie Elemente aus einer Sammlung entfernen möchten, während Sie darüber iterieren.
Glücklicherweise gibt es eine elegantere Lösung als das Schreiben einer for-Schleife, die unnötiges Tippen erfordert und fehleranfällig sein kann.
quelle
Reverse<T>()
erstellt einen Iterator, der die Liste rückwärts durchläuft, ihm jedoch zusätzlichen Puffer mit derselben Größe wie die Liste selbst zuweist ( referencesource.microsoft.com/#System.Core/System/Linq/… ).Reverse<T>
geht die ursprüngliche Liste nicht nur in umgekehrter Reihenfolge durch (ohne zusätzlichen Speicher zuzuweisen). Daher haben beideToList()
undReverse()
den gleichen Speicherverbrauch (beide erstellen eine Kopie),ToList()
tun aber nichts mit den Daten. MitReverse<int>()
würde ich mich fragen, warum die Liste aus welchem Grund umgekehrt ist.Reverse<T>()
einen neuen Puffer schafft. Ich bin mir nicht ganz sicher, ob ich verstehe, warum das notwendig ist. Es scheint mir, dass es abhängig von der zugrunde liegenden Struktur derEnumerable
zumindest in einigen Fällen möglich sein sollte, eine umgekehrte Iteration zu erreichen, ohne linearen Speicher zuzuweisen.Wenn Sie ".ToList ()" zu Ihrer Liste hinzufügen (oder die Ergebnisse einer LINQ-Abfrage), können Sie "item" direkt aus "list" entfernen, ohne dass die gefürchtete " Collection wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt ." Error. Der Compiler erstellt eine Kopie von "list", damit Sie das Array sicher entfernen können.
Obwohl dieses Muster nicht sehr effizient ist, fühlt es sich natürlich an und ist für fast jede Situation flexibel genug . Zum Beispiel, wenn Sie jedes "Element" in einer Datenbank speichern und erst dann aus der Liste entfernen möchten, wenn die DB-Speicherung erfolgreich ist.
quelle
Wählen Sie die gewünschten Elemente aus , anstatt zu versuchen, die nicht gewünschten Elemente zu entfernen . Dies ist so viel einfacher (und im Allgemeinen auch effizienter) als das Entfernen von Elementen.
Ich wollte dies als Kommentar als Antwort auf den Kommentar von Michael Dillon unten posten, aber es ist zu lang und wahrscheinlich nützlich, um es trotzdem in meiner Antwort zu haben:
Persönlich würde ich niemals Elemente einzeln entfernen, wenn Sie sie entfernen müssen, und dann einen Aufruf aufrufen,
RemoveAll
der ein Prädikat verwendet und das interne Array nur einmal neu anordnet, während für jedes Element, das Sie entfernen,Remove
eineArray.Copy
Operation ausgeführt wird.RemoveAll
ist weitaus effizienter.Und wenn Sie eine Liste rückwärts durchlaufen, haben Sie bereits den Index des Elements, das Sie entfernen möchten, sodass das Aufrufen weitaus effizienter ist
RemoveAt
, daRemove
zuerst die Liste durchlaufen wird, um den Index des Elements zu finden, das Sie verwenden Ich versuche zu entfernen, aber Sie kennen diesen Index bereits.Alles in allem sehe ich keinen Grund, jemals
Remove
eine for-Schleife aufzurufen . Und wenn es überhaupt möglich ist, verwenden Sie im Idealfall den obigen Code, um Elemente aus der Liste nach Bedarf zu streamen, sodass überhaupt keine zweite Datenstruktur erstellt werden muss.quelle
Wenn Sie ToArray () in einer generischen Liste verwenden, können Sie ein Entfernen (Element) in Ihrer generischen Liste ausführen:
quelle
Mit .ToList () wird eine Kopie Ihrer Liste erstellt, wie in dieser Frage erläutert: ToList () - Erstellt es eine neue Liste?
Mit ToList () können Sie aus Ihrer ursprünglichen Liste entfernen, da Sie tatsächlich über eine Kopie iterieren.
quelle
Wenn die Funktion, die bestimmt, welche Elemente gelöscht werden sollen, keine Nebenwirkungen hat und das Element nicht mutiert (es ist eine reine Funktion), lautet eine einfache und effiziente Lösung (lineare Zeit):
Wenn es Nebenwirkungen gibt, würde ich etwas verwenden wie:
Dies ist immer noch eine lineare Zeit, vorausgesetzt, der Hash ist gut. Aufgrund des Hash-Sets ist die Speichernutzung jedoch erhöht.
Wenn Ihre Liste nur eine
IList<T>
anstelle einer Liste ist,List<T>
schlage ich meine Antwort auf Wie kann ich diesen speziellen foreach-Iterator ausführen? . Dies hat eine lineare Laufzeit bei typischen Implementierungen vonIList<T>
im Vergleich zur quadratischen Laufzeit vieler anderer Antworten.quelle
Da jede Entfernung unter einer Bedingung vorgenommen wird, die Sie verwenden können
quelle
quelle
Sie können foreach nicht verwenden, aber Sie können vorwärts iterieren und Ihre Schleifenindexvariable verwalten, wenn Sie ein Element entfernen, wie folgt:
Beachten Sie, dass im Allgemeinen alle diese Techniken vom Verhalten der Sammlung abhängen, die iteriert wird. Die hier gezeigte Technik funktioniert mit der Standardliste (T). (Es ist durchaus möglich , Ihre eigene Sammlung Klasse zu schreiben und Iterator , dass tut erlaubt Artikel Entfernen während einer foreach - Schleife.)
quelle
Die Verwendung
Remove
oderRemoveAt
Bearbeitung einer Liste beim Durchlaufen dieser Liste wurde absichtlich erschwert, da dies fast immer falsch ist . Sie könnten es vielleicht mit einem cleveren Trick zum Laufen bringen, aber es wäre extrem langsam. Jedes Mal, wenn Sie anrufenRemove
, muss die gesamte Liste durchsucht werden, um das Element zu finden, das Sie entfernen möchten. Jedes Mal, wenn Sie aufrufenRemoveAt
, müssen die nachfolgenden Elemente um 1 Position nach links verschoben werden. Als solche würde jede Lösung, dieRemove
oder verwendetRemoveAt
, eine quadratische Zeit erfordern, O (n²) .Verwenden
RemoveAll
Sie, wenn Sie können. Andernfalls filtert das folgende Muster die Liste an Ort und Stelle in der linearen Zeit O (n) .quelle
Ich wünschte, das "Muster" wäre ungefähr so:
Dies würde den Code an dem Prozess ausrichten, der im Gehirn des Programmierers abläuft.
quelle
Nullable<bool>
wenn Sie nicht markierte zulassen möchten) und verwenden Sie es nach dem foreach, um Elemente zu entfernen / zu behalten.Unter der Annahme, dass das Prädikat eine boolesche Eigenschaft eines Elements ist, sollte das Element entfernt werden, wenn es wahr ist:
quelle
Ich würde die Liste aus einer LINQ-Abfrage neu zuweisen, die die Elemente herausfiltert, die Sie nicht behalten möchten.
Sofern die Liste nicht sehr umfangreich ist, sollten dabei keine wesentlichen Leistungsprobleme auftreten.
quelle
Der beste Weg, um Elemente aus einer Liste zu entfernen, während Sie darüber iterieren, ist die Verwendung
RemoveAll()
. Das Hauptanliegen der Menschen ist jedoch, dass sie einige komplexe Dinge innerhalb der Schleife tun und / oder komplexe Vergleichsfälle haben müssen.Die Lösung besteht darin
RemoveAll()
, diese Notation weiterhin zu verwenden, aber zu verwenden:quelle
Erstellen Sie einfach eine völlig neue Liste aus der ersten. Ich sage "Einfach" statt "Richtig", da das Erstellen einer völlig neuen Liste wahrscheinlich einen Leistungsaufschlag gegenüber der vorherigen Methode hat (ich habe mich nicht um Benchmarking gekümmert). Ich bevorzuge im Allgemeinen dieses Muster, es kann auch nützlich sein, um es zu überwinden Einschränkungen von Linq-To-Entities.
Auf diese Weise wird die Liste mit einer einfachen alten For-Schleife rückwärts durchlaufen. Dies vorwärts zu tun könnte problematisch sein, wenn sich die Größe der Sammlung ändert, aber rückwärts sollte immer sicher sein.
quelle
Kopieren Sie die Liste, die Sie iterieren. Dann aus der Kopie entfernen und das Original interatieren. Rückwärtsgehen ist verwirrend und funktioniert nicht gut, wenn Sie parallel schleifen.
quelle
Das würde mir gefallen
AUSGABE
quelle
Ich befand mich in einer ähnlichen Situation, in der ich jedes n- te Element in einer bestimmten entfernen musste
List<T>
.quelle
Die Kosten für das Entfernen eines Elements aus der Liste sind proportional zur Anzahl der Elemente, die auf das zu entfernende Element folgen. In dem Fall, in dem die erste Hälfte der Elemente zum Entfernen qualifiziert ist, muss jeder Ansatz, der auf dem individuellen Entfernen von Elementen basiert, etwa N * N / 4 Elementkopiervorgänge ausführen, was bei einer großen Liste sehr teuer werden kann .
Ein schnellerer Ansatz besteht darin, die Liste zu durchsuchen, um das erste zu entfernende Element zu finden (falls vorhanden), und dann von diesem Punkt an jedes Element, das aufbewahrt werden soll, an die Stelle zu kopieren, zu der es gehört. Wenn dies erledigt ist und R-Elemente beibehalten werden sollen, sind die ersten R-Elemente in der Liste diese R-Elemente, und alle Elemente, die gelöscht werden müssen, befinden sich am Ende. Wenn diese Elemente in umgekehrter Reihenfolge gelöscht werden, muss das System keines von ihnen kopieren. Wenn die Liste also N Elemente enthält, von denen R Elemente, einschließlich aller ersten F, beibehalten wurden, ist dies erforderlich Kopieren Sie RF-Elemente und verkleinern Sie die Liste um ein Element NR-mal. Alle lineare Zeit.
quelle
Mein Ansatz ist, dass ich zuerst eine Liste von Indizes erstelle, die gelöscht werden sollen. Danach durchlaufe ich die Indizes und entferne die Elemente aus der Anfangsliste. Das sieht so aus:
quelle
In C # können Sie einfach diejenigen markieren, die Sie löschen möchten, und dann eine neue Liste erstellen, über die Sie iterieren können ...
oder noch einfacher linq verwenden ....
Es lohnt sich jedoch zu überlegen, ob andere Aufgaben oder Threads gleichzeitig mit dem Entfernen Zugriff auf dieselbe Liste haben und stattdessen möglicherweise eine ConcurrentList verwenden.
quelle
Verfolgen Sie die zu entfernenden Elemente mit einer Eigenschaft und entfernen Sie sie alle nach dem Vorgang.
quelle
Ich wollte nur meine 2 Cent hinzufügen, falls dies jemandem hilft. Ich hatte ein ähnliches Problem, musste aber mehrere Elemente aus einer Array-Liste entfernen, während es wiederholt wurde. Die am höchsten bewertete Antwort hat dies größtenteils für mich getan, bis ich auf Fehler stieß und feststellte, dass der Index in einigen Fällen größer als die Größe der Array-Liste war, da mehrere Elemente entfernt wurden, der Index der Schleife jedoch nicht beibehalten wurde Spur davon. Ich habe dies mit einem einfachen Check behoben:
quelle
quelle
simples;
hier?