Rimuovi elementi da un elenco in un altro


206

Sto cercando di capire come attraversare un elenco generico di elementi che desidero rimuovere da un altro elenco di elementi.

Quindi diciamo che ho questo come esempio ipotetico

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

Voglio attraversare list1 con un foreach e rimuovere ogni elemento in List1, anch'esso contenuto in List2.

Non sono del tutto sicuro di come procedere poiché foreach non è basato sull'indice.


1
Vuoi rimuovere elementi in List1 che sono anche in List2?
Srinivas Reddy Thatiparthy

1
Cosa dovrebbe succedere se hai list1 = {foo1} e list2 = {foo1, foo1}. Tutte le copie di foo1 devono essere rimosse dall'elenco2 o solo la prima?
Mark Byers,

2
-1 - Ho votato a fondo su ogni risposta a questa domanda perché pensavo che fossero tutti sbagliati, ma sembra che la domanda sia stata posta orribilmente. Ora, non posso cambiarli - scuse. Vuoi rimuovere gli elementi list1che esistono in list2, o vuoi rimuovere gli elementi list2che esistono in list1? Al momento di questo commento, ogni risposta fornita eseguirà quest'ultima.
John Rasch,

7
@John Rashch, dovresti essere un po 'meno felice in quei downvotes. Alcune delle risposte sono abbastanza concettuali e dimostrano solo come ottenere ciò che l'OP vuole senza nemmeno fare riferimento agli elenchi citati nella domanda.
João Angelo,

3
@Mark - hai ragione, è tutta colpa mia - ecco perché ho messo qui il commento che spiega cosa è successo, stavo cercando una risposta precedente che nel frattempo avevo già avuto a una domanda simile dopo la mia votazione e che avrei lasciato commenti dopo che l'ho trovato - risulta che non è il processo migliore per questo!
John Rasch,

Risposte:


358

Puoi usare Tranne :

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

Probabilmente non hai nemmeno bisogno di quelle variabili temporanee:

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

Nota che Exceptnon modifica nessuno dei due elenchi: crea un nuovo elenco con il risultato.


13
Punto minore, ma questo produrrà un IEnumerable<car>, non un List<car>. È necessario chiamare ToList()per ottenere un elenco indietro. Inoltre, credo che dovrebbe essereGetSomeOtherList().Except(GetTheList()).ToList()
Adam Robinson,

9
Avrai anche bisogno using System.Linq;se non l'hai avuto prima.
yellavon,

1
Nota: list1.Except (list2) non darà lo stesso risultato di list2.Except (list1). L'ultimo ha funzionato per me.
Radbyx,

2
Fai solo attenzione quando utilizzi Exceptpoiché in realtà esegue un'operazione impostata , che distingue l'elenco risultante. Non mi aspettavo questo comportamento mentre sto usando un List, non un HashSet. Relazionato.
logan

4
Come mai questa è la risposta giusta? Sicuramente questo potrebbe darti quello che vuoi nel tuo contesto, tuttavia, "Rimuovere elementi da un elenco in un altro" non è certamente equivalente a un'operazione di differenza impostata e non dovresti disinformare le persone accettando questa come la risposta giusta !!!!
user1935724

37

Non è necessario un indice, poiché la List<T>classe consente di rimuovere gli elementi in base al valore anziché all'indice utilizzando la Removefunzione.

foreach(car item in list1) list2.Remove(item);

3
+1, ma IMO dovresti usare le parentesi attorno list2.Remove(item);all'istruzione.
ANeves

2
@sr pt: utilizzo sempre parentesi sulle istruzioni che compaiono su un'altra riga, ma non su blocchi a singola istruzione che posso / faccio posizionare sulla stessa riga dell'istruzione di controllo del flusso.
Adam Robinson,

4
@uriz: ignorando le qualifiche di ciò che sarebbe elegante, questa è l'unica risposta che fa effettivamente ciò che dice la domanda (rimuove gli elementi nell'elenco principale); l'altra risposta crea un nuovo elenco, che potrebbe non essere desiderabile se l'elenco viene passato da un altro chiamante che si aspetta che venga modificato invece di ottenere un elenco di sostituzione.
Adam Robinson,

5
@uriz @AdamRobinson da quando discutiamo di soluzioni eleganti ...list1.ForEach(c => list2.Remove(c));
David Sherret,

1
"elegante" dovrebbe significare "lo sviluppatore bloccato mantenendo questo codice lo troverà semplice e facile da capire", motivo per cui questa è la risposta migliore.
Seth,

22

Consiglierei di utilizzare i metodi di estensione LINQ . Puoi farlo facilmente con una riga di codice in questo modo:

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

Ciò presuppone ovviamente che gli oggetti in list1 che si stanno rimuovendo da list2 siano la stessa istanza.


2
Rimuove anche i duplicati.
Luglio,

17

Nel mio caso avevo due elenchi diversi, con un identificatore comune, un po 'come una chiave esterna. La seconda soluzione citata da "nzrytmn" :

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

È stato quello che si adattava meglio alla mia situazione. Avevo bisogno di caricare un DropDownList senza i record che erano già stati registrati.

Grazie !!!

Questo è il mio codice:

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();

non hai mai spiegato cosa fosse
DDlT3

15

Potresti usare LINQ, ma andrei con il RemoveAllmetodo. Penso che sia quello che meglio esprime il tuo intento.

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

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

integers.RemoveAll(i => remove.Contains(i));

9
O ancora più semplice con i gruppi di metodi che puoi fare: interi.RemoveAll (remove.Contains);
Ryan,

12
list1.RemoveAll(l => list2.Contains(l));

alias "totalmente impuro" :-)
Xan-Kun Clark-Davis il

Cosa c'è che non va. Sembra meglio quindi creare un altro elenco utilizzando Tranne. Soprattutto quando entrambi gli elenchi sono molto piccoli.
Mike Keskinov,

1
Dal momento che entrambi i metodi di elenco sono O(N), ciò comporterà O(N^2)un problema con elenchi di grandi dimensioni.
Tigrou,

7

Soluzione 1: puoi fare così:

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

Ma in alcuni casi questa soluzione potrebbe non funzionare. se non funziona puoi usare la mia seconda soluzione.

Soluzione 2:

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

facciamo finta che list1 sia il tuo elenco principale e list2 sia il tuo elenco secondario e desideri ottenere elementi di elenco1 senza elementi di elenco2.

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

0

Poiché Exceptnon modifica l'elenco, è possibile utilizzare ForEach su List<T>:

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

Potrebbe non essere il modo più efficiente, ma è semplice, quindi leggibile e aggiorna l'elenco originale (che è il mio requisito).


-3

Ecco qua ..

    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);
            }
        });

3
-1. Ciò genererà un'eccezione dopo la rimozione del primo elemento. Inoltre, è (generalmente) un'idea migliore attraversare l'elenco da rimuovere , poiché di solito è più piccolo. Stai anche forzando più attraversatori di elenchi a farlo in questo modo.
Adam Robinson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.