Usuń elementy z jednej listy na drugiej

206

Próbuję wymyślić, jak przejrzeć ogólną listę elementów, które chcę usunąć z innej listy elementów.

Powiedzmy, że mam to jako hipotetyczny przykład

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

Chcę przejść listę 1 z foreach i usunąć każdy element z Listy 1, który jest również zawarty w Liście 2.

Nie jestem do końca pewien, jak to zrobić, ponieważ foreach nie jest oparty na indeksach.

PositiveGuy
źródło
1
Chcesz usunąć elementy z Listy 1, które również znajdują się na Liście 2?
Srinivas Reddy Thatiparthy
1
Co powinno się stać, jeśli masz list1 = {foo1} i list2 = {foo1, foo1}. Czy wszystkie kopie foo1 powinny zostać usunięte z list2, czy tylko pierwsza?
Mark Byers
2
-1 - Głosowałem za każdą odpowiedzią w tym pytaniu, ponieważ myślałem, że wszystkie były w błędzie, ale wygląda na to, że pytanie zostało po prostu okropnie zadane. Teraz nie mogę ich zmienić - przepraszam. Czy chcesz usunąć elementy, list1które istnieją w list2, czy chcesz usunąć elementy, list2które istnieją w list1? W momencie tego komentarza każda podana odpowiedź wykona to drugie.
John Rasch
7
@John Rashch, powinieneś być trochę mniej zadowolony z tych negatywnych opinii. Niektóre odpowiedzi są dość koncepcyjne i pokazują jedynie, jak osiągnąć to, czego chce PO, nawet bez odniesień do list wymienionych w pytaniu.
João Angelo
3
@ Mark - masz rację, moja wina całkowicie - dlatego umieściłem tutaj komentarz wyjaśniający, co się stało, szukałem poprzedniej odpowiedzi, na którą miałem już podobne pytanie w międzyczasie po głosowaniu i zamierzałem odejść komentarze po tym, jak go znalazłem - okazuje się, że nie jest to najlepszy proces!
John Rasch

Odpowiedzi:

358

Możesz użyć Z wyjątkiem :

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

Prawdopodobnie nie potrzebujesz nawet tych zmiennych tymczasowych:

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

Pamiętaj, że Exceptnie modyfikuje żadnej z list - tworzy nową listę z wynikiem.

Mark Byers
źródło
13
Drobny punkt, ale spowoduje to IEnumerable<car>, a nie List<car>. Musisz zadzwonić, ToList()aby odzyskać listę. Ponadto uważam, że tak powinno byćGetSomeOtherList().Except(GetTheList()).ToList()
Adam Robinson
9
Będziesz także potrzebować, using System.Linq;jeśli wcześniej go nie miałeś.
yellavon,
1
Uwaga: list1.Except (list2) nie da takiego samego wyniku jak list2.Except (list1). Ostatni działał dla mnie.
radbyx
2
Zachowaj ostrożność podczas używania, Exceptponieważ faktycznie wykonuje ona operację ustawiania , która odróżnia wynikową listę. Nie spodziewałem się takiego zachowania, ponieważ używam, a Listnie HashSet. Związane z.
logan
4
Dlaczego to jest poprawna odpowiedź? Jasne, że może to dać ci to, czego chcesz w swoim kontekście, jednak „Usuń elementy z jednej listy na drugiej” z pewnością nie jest równoważne operacji ustawiania różnicy i nie powinieneś wprowadzać w błąd ludzi, akceptując to jako prawidłową odpowiedź !!!!
user1935724
37

Nie potrzebujesz indeksu, ponieważ List<T>klasa umożliwia usuwanie elementów według wartości zamiast indeksowania za pomocą Removefunkcji.

foreach(car item in list1) list2.Remove(item);
Adam Robinson
źródło
3
+1, ale IMO powinna używać nawiasów wokół list2.Remove(item);instrukcji.
ANeves
2
@sr pt: Zawsze używam nawiasów w instrukcjach, które pojawiają się w innym wierszu, ale nie w blokach pojedynczych instrukcji, które mogę / mogę umieścić w tym samym wierszu, co instrukcja kontroli przepływu.
Adam Robinson
4
@uriz: Pomijając kwalifikacje tego, co byłoby eleganckie, jest to jedyna odpowiedź, która faktycznie robi to, co mówi pytanie (usuwa pozycje z głównej listy); druga odpowiedź tworzy nową listę, co może nie być pożądane, jeśli lista jest przekazywana przez innego wywołującego, który oczekuje, że zostanie zmodyfikowany zamiast uzyskać listę zastępczą.
Adam Robinson
5
@uriz @AdamRobinson, ponieważ rozmawiamy o eleganckich rozwiązaniach ...list1.ForEach(c => list2.Remove(c));
David Sherret
1
„elegancki” powinien oznaczać „programiści, którzy utknęli w utrzymywaniu tego kodu, uznają go za prosty i łatwy do zrozumienia”, dlatego jest to najlepsza odpowiedź.
Seth
22

Polecam korzystanie z metod rozszerzenia LINQ . Możesz to łatwo zrobić za pomocą jednego wiersza kodu:

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

Zakłada się oczywiście, że obiekty z listy 1, które usuwasz z listy 2, są tą samą instancją.

Berkshire
źródło
2
Usuwa również duplikaty.
lipiec Zwyczajny
17

W moim przypadku miałem dwie różne listy, ze wspólnym identyfikatorem, trochę jak klucz obcy. Drugie rozwiązanie cytowane przez „nzrytmn” :

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

Był tym, który najlepiej pasował do mojej sytuacji. Musiałem załadować DropDownList bez rekordów, które zostały już zarejestrowane.

Dziękuję Ci !!!

To jest mój kod:

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
źródło
nigdy nie wyjaśniłeś, czym był
DDlT3
15

Możesz użyć LINQ, ale wybrałbym RemoveAllmetodę. Myślę, że to ten, który lepiej wyraża twoją intencję.

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
źródło
9
Lub jeszcze prościej z grupami metod, które możesz zrobić - liczby całkowite.RemoveAll (remove.Contains);
Ryan
12
list1.RemoveAll(l => list2.Contains(l));
Alexandre Amado de Castro
źródło
aka „totalnie nieczyste” :-)
Xan-Kun Clark-Davis
Co z tym nie tak. Wygląda lepiej niż tworzenie kolejnej listy za pomocą wyjątku. Zwłaszcza, gdy obie listy są bardzo małe.
Mike Keskinov,
1
Ponieważ obie metody list są O(N), doprowadzi to do O(N^2)problemu z dużymi listami.
tigrou
7

Rozwiązanie 1: Możesz to zrobić w następujący sposób:

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

Ale w niektórych przypadkach to rozwiązanie może nie działać. jeśli to nie działa, możesz użyć mojego drugiego rozwiązania.

Rozwiązanie 2:

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

udajemy, że lista1 jest twoją główną listą, a lista2 to twoja lista pomocnicza i chcesz dostać pozycje z listy 1 bez pozycji z listy2.

 var result =  list1.Where(p => !list2.Any(x => x.ID == p.ID && x.property1 == p.property1)).ToList();
nzrytmn
źródło
0

Ponieważ Exceptnie modyfikuje listy, możesz użyć ForEach na List<T>:

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

Może nie jest to najbardziej efektywny sposób, ale jest prosty, dlatego czytelny i aktualizuje oryginalną listę (co jest moim wymaganiem).

Necriis
źródło
-3

Proszę bardzo ..

    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.
źródło
3
-1. Spowoduje to zgłoszenie wyjątku po usunięciu pierwszego elementu. Ponadto (ogólnie) lepszym pomysłem jest przeglądanie listy w celu jej usunięcia , ponieważ zwykle jest ona mniejsza. Zmuszasz także więcej osób do przechodzenia przez listę, robiąc to w ten sposób.
Adam Robinson