Frage

Ich bin für ein besseren Muster für eine Liste von Elementen arbeiten, die jeweils Bedarf verarbeitet und dann je nach dem Ergebnis entfernt werden aus der Liste.

Sie können nicht .Remove(element) in einem foreach (var element in X) verwenden (weil es in Collection was modified; enumeration operation may not execute. Ausnahme führt) ... Sie auch nicht for (int i = 0; i < elements.Count(); i++) und .RemoveAt(i) verwenden können, weil es Ihre aktuelle Position in der Sammlung in Bezug auf i stört.

Gibt es eine elegante Art und Weise, dies zu tun?

War es hilfreich?

Lösung

Iterate Ihre Liste in umgekehrter Richtung mit einem for-Schleife:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

Beispiel:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Alternativ können Sie die RemoveAll Methode mit einem Prädikat Test gegen:

safePendingList.RemoveAll(item => item.Value == someValue);

Hier ist ein vereinfachtes Beispiel zu demonstrieren:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

Andere Tipps

Eine einfache und unkomplizierte Lösung:

Verwenden Sie ein Standard-for-Schleife laufen zurück auf Ihre Sammlung und RemoveAt(i) auf Elemente zu entfernen.

Reverse-Iteration sollte das erste, was zu in den Sinn kommen, wenn Sie Elemente aus einer Sammlung entfernen möchten, während sie über sie iterieren.

Zum Glück gibt es eine elegantere Lösung als ein for-Schleife schreiben, die unnötige Eingabe beinhaltet und kann fehleranfällig sein.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

Wenn Sie hinzufügen „.ToList ()“ , um Ihre Liste (oder die Ergebnisse einer LINQ-Abfrage), können Sie entfernen ‚item‘ direkt aus ‚Liste‘ ohne den gefürchteten „ Die Auflistung wurde geändert; Enumerationsvorgang nicht ausgeführt werden kann „. Error. Der Compiler erstellt eine Kopie von „Liste“, so dass Sie sicher das Entfernen auf dem Array tun.

Während dieses Muster ist nicht sehr effizient, es hat ein natürliches Gefühl und ist flexibel genug für fast jede Situation . Zum Beispiel, wenn Sie wollen jeden „Punkt“ zu einem DB speichern und sie aus der Liste entfernen, nur wenn die DB speichert erfolgreich ist.

Mit dem ToArray () auf einer generischen Liste können Sie ein Remove (Artikel) auf generische Liste tun:

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

Wählen Sie die Elemente, die Sie tun wollen, anstatt zu versuchen, die Elemente, die Sie nicht entfernen möchten. Das ist so viel einfacher (und in der Regel effizienter zu) als Elemente zu entfernen.

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

Das wollte ich als Kommentar in Antwort senden unten auf den Kommentar verfasst von Michael Dillon, aber es ist zu lang und wahrscheinlich nützlich sowieso in meiner Antwort zu haben:

Persönlich würde ich nie Elemente entfernen one-by-one, wenn Sie Notwendigkeit der Entfernung zu tun, dann RemoveAll aufrufen, die ein Prädikat nimmt und ordnet nur die interne Array einmal, während Remove tut ein Array.Copy Betrieb für jedes Element, das Sie entfernen. RemoveAll ist wesentlich effizienter zu gestalten.

Und wenn Sie rückwärts über eine Liste laufen, haben Sie bereits den Index des Elements, das Sie entfernen möchten, so ist es weit effizienter Anruf RemoveAt wäre, weil Remove zuerst ein Durchlauf der Liste nicht zu finden die Index des Elements Sie versuchen zu entfernen, aber Sie bereits diesen Index kennen.

Also alles in allem, ich sehe keinen Grund, jemals Anruf Remove in einer for-Schleife. Und im Idealfall, wenn es überhaupt möglich ist, verwenden Sie die obigen Code-Elemente aus der Liste zu streamen wie nötig, so dass keine zweiten Datenstruktur hat überhaupt geschaffen werden.

Mit .ToList () wird eine Kopie der Liste machen, erklärt, wie in dieser Frage: ToList () - Gibt es eine neue Liste erstellen

Durch die Verwendung von ToList (), können Sie von Ihrer ursprünglichen Liste entfernen, weil Sie eigentlich eine Kopie iterieren.

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }

Wenn die Funktion, die bestimmt, keine Nebenwirkungen, welche Elemente zu löschen und mutieren nicht das Element (es ist eine reine Funktion), eine einfache und effiziente (lineare Zeit) Lösung ist:

list.RemoveAll(condition);

Wenn es Nebenwirkungen sind, wie ich so etwas wie verwenden würde:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

Dies ist immer noch die lineare Zeit, vorausgesetzt, der Hash gut ist. Aber es hat einen erhöhten Speicherverbrauch aufgrund der Hashset.

Schließlich, wenn Ihre Liste ist nur ein IList<T> anstelle eines List<T> schlage ich meine Antwort auf Wie kann ich diese spezielle foreach iterator tun? . Dies wird lineare Laufzeit hat typische Implementierungen von IList<T> gegeben, verglichen mit quadratischer Laufzeit von vielen anderen Antworten.

Wie jedes Entfernen einer Bedingung genommen wird können Sie

list.RemoveAll(item => item.Value == someValue);
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));

Sie können nicht foreach verwenden, aber man konnte nach vorne durchlaufen und Ihrer Schleife Indexvariable verwalten, wenn Sie ein Element entfernen, etwa so:

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

Beachten Sie, dass in der Regel alle diese Techniken beruhen auf dem Verhalten der Sammlung iteriert werden. Die Technik, die hier gezeigt wird mit der Standardliste (T) arbeiten. (Es ist durchaus möglich, Ihre eigene Sammlung Klasse zu schreiben und Iterator, dass hat erlaubt Artikel Entfernen während einer foreach-Schleife).

Mit Remove oder RemoveAt auf einer Liste, während Iterieren diese Liste über absichtlich schwer gemacht, weil es fast immer die falsche Sache zu tun. Sie könnten in der Lage sein, es mit einigem cleveren Trick zum Laufen zu bringen, aber es wäre sehr langsam sein. Jedes Mal, wenn Remove sie die gesamte Liste aufrufen scannen durch das Element, das Sie entfernen möchten, zu finden. Jedes Mal, wenn Sie RemoveAt es nennen muss nachfolgende Elemente 1 Position nach links bewegen. Als solche eine Lösung Remove oder RemoveAt verwenden, würde quadratische Zeit erfordern, O (n²) .

Mit RemoveAll wenn Sie können. Andernfalls wird die folgende Muster die Liste filtern in-place in linearer Zeit, O (n) .

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

I Wunsch die "Muster" war so etwas wie folgt aus:

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

Dies würde den Code mit dem Prozess auszurichten, die auf das Gehirn in der Programmierer geht.

Ich würde die Liste von einer LINQ-Abfrage neu zuweisen, dass aus den Elementen gefiltert Sie nicht wollen, zu halten.

list = list.Where(item => ...).ToList();

Es sei denn, die Liste ist sehr groß sollte es keine signifikanten Performance-Probleme sein, dies zu tun.

Die beste Art und Weise Elemente aus einer Liste zu entfernen, während über sie iterieren zu verwenden ist RemoveAll() . Aber das Hauptanliegen von Menschen geschrieben ist, dass sie einige komplexe Dinge innerhalb der Schleife zu tun haben, und / oder haben komplexe vergleichen Fälle.

Die Lösung ist immer noch RemoveAll() , aber diese Notation verwenden:

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

Unter der Annahme, dass Prädikat ist eine Boolesche Eigenschaft eines Elements, dass, wenn es wahr ist, dann sollte das Element entfernt werden:

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

Sie einfach eine völlig neue Liste von der ersten erstellen. Ich sage „Easy“ und nicht als „Recht“ als eine völlig neue Liste erstellen wahrscheinlich bei einer Leistung Prämie gegenüber dem bisherigen Verfahren kommt (ich nicht mit einem Benchmarking gestört habe.) Ich dieses Muster im Allgemeinen bevorzuge, ist es auch nützlich sein kann bei der Überwindung Linq-To-Entitäten Einschränkungen.

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

Auf diese Weise durchläuft die Liste rückwärts mit einem einfachen alten For-Schleife. wenn die Größe der Sammlung Änderungen problematisch sein, könnte dies nach vorne zu tun, aber nach hinten sollte immer sicher sein.

Ich fand mich in einer ähnlichen Situation, wo ich jeden n entfernen musste th Element in einem bestimmten List<T>.

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

Die Kosten für ein Element aus der Liste zu entfernen, ist proportional zu der Anzahl der Elemente nach dem anderen entfernt werden. In dem Fall, dass die erste Hälfte der Elemente für die Entfernung zu qualifizieren, jeden Ansatz, die beim Entfernen einzeln Artikels basiert wird am Ende mit etwa N * N / 4 Artikel-Kopiervorgängen durchzuführen, was sehr teuer werden kann, wenn die Liste groß ist .

Eine schnellere Methode ist durch die Liste scannen das ersten Elemente finden entfernt werden (falls vorhanden), und dann von diesem Punkt kopiert vorwärts jedes Element, das an Ort und Stelle festgehalten werden sollte, wo es hingehört. Sobald dies geschehen ist, wenn R Elemente beibehalten werden soll, werden die ersten R Elemente in der Liste jene R Elemente sein, und alle Elemente erfordern Löschung am Ende sein wird. Wenn diese Elemente in umgekehrter Reihenfolge gelöscht werden, wird das System nicht am Ende mit einen von ihnen zu kopieren, so dass, wenn die Liste hatte N Elemente, von denen R Gegenstände, darunter alle des ersten F, wurden beibehalten, es wird notwendig sein, R-F-Elemente und schrumpft die Liste durch ein Element N-R-mal zu kopieren. Alle lineare Zeit.

Mein Ansatz ist, dass ich zum ersten Mal eine Liste von Indizes erstellen, die gelöscht werden sollen. Danach I Schleife über die Indizes und die Elemente aus der ursprünglichen Liste entfernen. Das sieht wie folgt aus:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

Kopieren Sie die Liste, die Sie Iterieren sind. Entfernen Sie dann aus der Kopie und Interate das Original. Rückwärts verwirrend ist und gut funktioniert nicht, wenn parallel Looping.

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

In C # eine einfache Möglichkeit ist die, die markieren Sie dann eine neue Liste über zu Iterierte erstellen löschen wollen ...

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

oder noch einfacher Gebrauch Linq ....

list.RemoveAll(p=>p.Delete);

, aber es ist eine Überlegung wert, wenn andere Aufgaben oder Threads Zugriff auf die gleiche Liste zur gleichen Zeit haben, werden Sie beschäftigt sind zu entfernen, und vielleicht einen ConcurrentList anstelle.

Trace die Elemente mit einer Eigenschaft zu entfernen, und entfernen Sie sie alle nach dem Prozess.

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

Ich wollte nur meine 2 Cent dazu bei hinzuzufügen dies jemand hilft, ich hatte ein ähnliches Problem, aber benötigt mehrere Elemente aus einer Array-Liste zu entfernen, während es über iteriert wurde. die höchste upvoted Antwort hat es für mich zum größten Teil bis ich in Fehler lief und erkennen, dass der Index als die Größe der Array-Liste größer war in einigen Fällen, weil mehrere Elemente aber der Index der Schleife nicht halten hat wurden entfernt werden Spur dieses. Ich reparierte diese mit einem einfachen Test:

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

for(int i = place_holder.Count-1; i>= 0; i--){
    if(i>= place_holder.Count){
        i = place_holder.Count-1; 
    }

// some method that removes multiple elements here
}
myList.RemoveAt(i--);

simples;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top