Entfernen Sie Elemente aus einer Liste in einer anderen

206

Ich versuche herauszufinden, wie eine generische Liste von Elementen durchlaufen wird, die ich aus einer anderen Liste von Elementen entfernen möchte.

Nehmen wir also an, ich habe dies als hypothetisches Beispiel

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();

Ich möchte list1 mit einem foreach durchlaufen und jedes Element in List1 entfernen, das auch in List2 enthalten ist.

Ich bin mir nicht ganz sicher, wie ich das anstellen soll, da foreach nicht indexbasiert ist.

PositiveGuy
quelle
1
Sie möchten Elemente in Liste1 entfernen, die sich auch in Liste2 befinden?
Srinivas Reddy Thatiparthy
1
Was soll passieren, wenn Sie list1 = {foo1} und list2 = {foo1, foo1} haben? Sollten alle Kopien von foo1 aus Liste2 entfernt werden oder nur die erste?
Mark Byers
2
-1 - Ich habe jede Antwort in dieser Frage abgelehnt, weil ich dachte, sie wären alle falsch, aber es scheint, dass die Frage nur schrecklich gestellt wird. Jetzt kann ich sie nicht ändern - Entschuldigung. Möchten Sie die Elemente entfernen list1, die in vorhanden sind list2, oder möchten Sie die Elemente entfernen list2, die in vorhanden sind list1? Zum Zeitpunkt dieses Kommentars führt jede Antwort die letztere aus.
John Rasch
7
@ John Rashch, du solltest ein bisschen weniger glücklich über diese Abstimmungen sein. Einige der Antworten sind ziemlich konzeptionell und zeigen nur, wie man das erreicht, was das OP will, ohne sich auf die in der Frage genannten Listen zu beziehen.
João Angelo
3
@Mark - Sie haben Recht, meine Schuld ist völlig - deshalb habe ich hier den Kommentar eingefügt, in dem erklärt wird, was passiert ist. Ich habe nach einer früheren Antwort gesucht, die ich bereits in der Zwischenzeit nach meiner Abstimmung auf eine ähnliche Frage hatte, und wollte gehen Kommentare, nachdem ich es gefunden habe - es stellt sich heraus, dass dies nicht der beste Prozess dafür ist!
John Rasch

Antworten:

358

Sie können Außer verwenden :

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();
List<car> result = list2.Except(list1).ToList();

Sie benötigen wahrscheinlich nicht einmal diese temporären Variablen:

List<car> result = GetSomeOtherList().Except(GetTheList()).ToList();

Beachten Sie, dass Exceptkeine der beiden Listen geändert wird. Es wird eine neue Liste mit dem Ergebnis erstellt.

Mark Byers
quelle
13
Kleiner Punkt, aber dies wird ein IEnumerable<car>, kein a erzeugen List<car>. Sie müssen anrufen ToList(), um eine Liste zurück zu bekommen. Darüber hinaus glaube ich, dass es sein sollteGetSomeOtherList().Except(GetTheList()).ToList()
Adam Robinson
9
Sie werden es auch brauchen, using System.Linq;wenn Sie es vorher nicht hatten.
Yellavon
1
Hinweis: list1.Except (list2) liefert nicht das gleiche Ergebnis wie list2.Except (list1). Der letzte hat bei mir funktioniert.
Radbyx
2
Seien Sie vorsichtig, wenn Sie verwenden, Exceptda dies tatsächlich eine festgelegte Operation ausführt , die die resultierende Liste unterscheidet. Ich habe dieses Verhalten nicht erwartet, da ich a verwende List, nicht a HashSet. Verbunden.
Logan
4
Wie kommt es, dass dies die richtige Antwort ist? Sicher, dies könnte Ihnen das geben, was Sie in Ihrem Kontext wollen, aber "Elemente aus einer Liste in einer anderen entfernen" ist sicherlich nicht gleichbedeutend mit einer festgelegten Differenzoperation, und Sie sollten die Leute nicht falsch informieren, indem Sie dies als die richtige Antwort akzeptieren !!!!
user1935724
37

Sie benötigen keinen Index, da Sie mit der List<T>Klasse Elemente mithilfe der RemoveFunktion nach Wert und nicht nach Index entfernen können .

foreach(car item in list1) list2.Remove(item);
Adam Robinson
quelle
3
+1, aber IMO sollten Sie Klammern um die list2.Remove(item);Anweisung verwenden.
Aneves
2
@sr pt: Ich verwende immer Klammern für Anweisungen, die in einer anderen Zeile erscheinen, aber nicht für Blöcke mit einzelnen Anweisungen, die ich in derselben Zeile wie die Flusssteuerungsanweisung platzieren kann / kann.
Adam Robinson
4
@uriz: Abgesehen von den Qualifikationen dessen, was elegant wäre, ist dies die einzige Antwort, die tatsächlich das tut, was die Frage sagt (entfernt die Elemente in der Hauptliste); Mit der anderen Antwort wird eine neue Liste erstellt. Dies ist möglicherweise nicht wünschenswert, wenn die Liste von einem anderen Anrufer übergeben wird, der erwartet, dass sie geändert wird, anstatt eine Ersatzliste zu erhalten.
Adam Robinson
5
@uriz @AdamRobinson, da wir über elegante Lösungen diskutieren ...list1.ForEach(c => list2.Remove(c));
David Sherret
1
"elegant" sollte bedeuten, dass "der Entwickler, der diesen Code nicht mehr pflegt, ihn einfach und leicht verständlich findet", weshalb dies die beste Antwort ist.
Seth
22

Ich würde empfehlen, die LINQ-Erweiterungsmethoden zu verwenden . Sie können dies einfach mit einer Codezeile wie folgt tun:

list2 = list2.Except(list1).ToList();

Dies setzt natürlich voraus, dass die Objekte in Liste1, die Sie aus Liste2 entfernen, dieselbe Instanz sind.

Berkshire
quelle
2
Es werden auch Duplikate entfernt.
Juli Juli
17

In meinem Fall hatte ich zwei verschiedene Listen mit einer gemeinsamen Kennung, ähnlich einem Fremdschlüssel. Die zweite von "nzrytmn" zitierte Lösung :

var result =  list1.Where(p => !list2.Any(x => x.ID == p.ID && x.property1 == p.property1)).ToList();

War derjenige, der am besten in meine Situation passte. Ich musste eine DropDownList ohne die bereits registrierten Datensätze laden.

Danke !!!

Das ist mein Code:

t1 = new T1();
t2 = new T2();

List<T1> list1 = t1.getList();
List<T2> list2 = t2.getList();

ddlT3.DataSource= list2.Where(s => !list1.Any(p => p.Id == s.ID)).ToList();
ddlT3.DataTextField = "AnyThing";
ddlT3.DataValueField = "IdAnyThing";
ddlT3.DataBind();
Gabriel Santos Reis
quelle
Sie haben nie erklärt, was DDlT3 war
Rogue39nin
14

Sie könnten LINQ verwenden, aber ich würde mit RemoveAllMethode gehen . Ich denke, das ist derjenige, der Ihre Absicht besser zum Ausdruck bringt.

var integers = new List<int> { 1, 2, 3, 4, 5 };

var remove = new List<int> { 1, 3, 5 };

integers.RemoveAll(i => remove.Contains(i));
João Angelo
quelle
8
Oder noch einfacher mit Methodengruppen, die Sie ausführen können - integers.RemoveAll (remove.Contains);
Ryan
12
list1.RemoveAll(l => list2.Contains(l));
Alexandre Amado de Castro
quelle
aka "total unrein" :-)
Xan-Kun Clark-Davis
Was ist daran falsch? Es sieht besser aus, als mit Except eine weitere Liste zu erstellen. Besonders wenn beide Listen sehr klein sind.
Mike Keskinov
1
Da es sich bei beiden Listenmethoden um solche handelt O(N), kann dies O(N^2)bei großen Listen zu Problemen führen.
Tigrou
7

Lösung 1: Sie können Folgendes tun:

List<car> result = GetSomeOtherList().Except(GetTheList()).ToList();

In einigen Fällen funktioniert diese Lösung jedoch möglicherweise nicht. Wenn es nicht funktioniert, können Sie meine zweite Lösung verwenden.

Lösung 2:

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();

Wir geben vor, dass list1 Ihre Hauptliste und list2 Ihre Secondry-Liste ist und Sie Elemente von list1 ohne Elemente von list2 erhalten möchten.

 var result =  list1.Where(p => !list2.Any(x => x.ID == p.ID && x.property1 == p.property1)).ToList();
nzrytmn
quelle
0

Da Exceptdie Liste nicht geändert wird , können Sie ForEach verwenden für List<T>:

list2.ForEach(item => list1.Remove(item));

Es ist vielleicht nicht der effizienteste Weg, aber es ist einfach, daher lesbar und aktualisiert die ursprüngliche Liste (was meine Anforderung ist).

Necriis
quelle
-3

Hier gehts ..

    List<string> list = new List<string>() { "1", "2", "3" };
    List<string> remove = new List<string>() { "2" };

    list.ForEach(s =>
        {
            if (remove.Contains(s))
            {
                list.Remove(s);
            }
        });
Ian P.
quelle
3
-1. Dies löst eine Ausnahme aus, nachdem das erste Element entfernt wurde. Außerdem ist es (im Allgemeinen) eine bessere Idee, die zu entfernende Liste zu durchlaufen , da sie normalerweise kleiner ist. Sie erzwingen auch mehr Listenüberquerungen auf diese Weise.
Adam Robinson