Eliminar elementos de una lista en otra

206

Estoy tratando de descubrir cómo atravesar una lista genérica de elementos que quiero eliminar de otra lista de elementos.

Digamos que tengo esto como un ejemplo hipotético

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

Quiero atravesar list1 con un foreach y eliminar cada elemento en List1 que también está contenido en List2.

No estoy muy seguro de cómo hacerlo, ya que foreach no está basado en índices.

PositivoGuy
fuente
1
¿Desea eliminar elementos en List1 que también están en List2?
Srinivas Reddy Thatiparthy
1
¿Qué debería suceder si tiene list1 = {foo1} y list2 = {foo1, foo1}? ¿Deberían eliminarse todas las copias de foo1 de la lista2, o solo la primera?
Mark Byers
2
-1 - He rechazado todas las respuestas en esta pregunta porque pensé que estaban todas equivocadas, pero parece que la pregunta se hizo horriblemente. Ahora, no puedo cambiarlos, disculpas. ¿Desea eliminar los elementos list1que existen en list2o desea eliminar los elementos list2que existen en list1? En el momento de este comentario, cada respuesta proporcionada realizará la última.
John Rasch
77
@ John Rashch, deberías estar un poco menos feliz con esos votos negativos. Algunas de las respuestas son bastante conceptuales y solo demuestran cómo lograr lo que quiere el OP sin siquiera relacionarse con las listas mencionadas en la pregunta.
João Angelo
3
@ Mark - tienes razón, es mi culpa por completo - por eso puse el comentario aquí explicando lo que sucedió, estaba buscando una respuesta anterior que ya había tenido una pregunta similar mientras tanto después de mi votación y me iba a ir comentarios después de que lo encontré, ¡resulta que ese no es el mejor proceso para esto!
John Rasch

Respuestas:

358

Puedes usar Excepto :

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

Probablemente ni siquiera necesite esas variables temporales:

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

Tenga en cuenta que Exceptno modifica ninguna de las listas: crea una nueva lista con el resultado.

Mark Byers
fuente
13
Punto menor, pero esto producirá un IEnumerable<car>, no un List<car>. Debe llamar ToList()para recuperar una lista. Además, creo que debería serGetSomeOtherList().Except(GetTheList()).ToList()
Adam Robinson
9
También lo necesitará using System.Linq;si no lo tenía antes.
yellavon
1
Nota: list1.Except (list2) no dará el mismo resultado que list2.Except (list1). El último funcionó para mí.
radbyx
2
Solo tenga cuidado al usarlo, Exceptya que esto realmente realiza una operación de configuración , que distingue la lista resultante. No esperaba este comportamiento ya que estoy usando a List, no a HashSet. Relacionado.
Logan
44
¿Cómo es que esta es la respuesta correcta? Seguro que esto puede darle lo que desea en su contexto, sin embargo, "Eliminar elementos de una lista en otra" ciertamente no es equivalente a una operación de diferencia establecida, ¡y no debe desinformar a las personas al aceptar esto como la respuesta correcta!
user1935724
37

No necesita un índice, ya que la List<T>clase le permite eliminar elementos por valor en lugar de índice mediante la Removefunción.

foreach(car item in list1) list2.Remove(item);
Adam Robinson
fuente
3
+1, pero en mi opinión, debe usar corchetes alrededor de la list2.Remove(item);declaración.
ANeves
2
@sr pt: siempre uso corchetes en las declaraciones que aparecen en otra línea, pero no en los bloques de una sola declaración que puedo / coloco en la misma línea que la declaración de control de flujo.
Adam Robinson
44
@uriz: sin tener en cuenta las calificaciones de lo que sería elegante, esta es la única respuesta que realmente hace lo que dice la pregunta (elimina los elementos de la lista principal); la otra respuesta crea una nueva lista, que puede no ser deseable si la lista se pasa de una persona que llama diferente que espera que se modifique en lugar de obtener una lista de reemplazo.
Adam Robinson
55
@uriz @AdamRobinson ya que estamos discutiendo soluciones elegantes ...list1.ForEach(c => list2.Remove(c));
David Sherret
1
"elegante" debería significar "el desarrollador atascado en mantener este código lo encontrará simple y fácil de entender", razón por la cual esta es la mejor respuesta.
Seth
22

Recomendaría usar los métodos de extensión LINQ . Puede hacerlo fácilmente con una línea de código de esta manera:

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

Esto supone, por supuesto, que los objetos en list1 que está eliminando de list2 son la misma instancia.

Berkshire
fuente
2
También elimina duplicados.
Julio
17

En mi caso, tenía dos listas diferentes, con un identificador común, algo así como una clave foránea. La segunda solución citada por "nzrytmn" :

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

Fue el que mejor encajó en mi situación. Necesitaba cargar una DropDownList sin los registros que ya se habían registrado.

Gracias !!!

Este es mi código:

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
fuente
ou nunca explicó lo que era DDlT3
rogue39nin
15

Podrías usar LINQ, pero yo iría con el RemoveAllmétodo. Creo que es el que mejor expresa tu intención.

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
fuente
9
O incluso más simple con los grupos de métodos que puede hacer: números enteros. Eliminar todo (remove.Contains);
Ryan
12
list1.RemoveAll(l => list2.Contains(l));
Alexandre Amado de Castro
fuente
alias "totalmente impuro" :-)
Xan-Kun Clark-Davis
Que está mal con eso. Se ve mejor que crear otra lista usando Excepto. Especialmente cuando ambas listas son muy pequeñas.
Mike Keskinov
1
Dado que ambos métodos de lista son O(N), esto conducirá a lo O(N^2)que podría ser un problema con listas grandes.
tigrou
7

Solución 1: puede hacer esto:

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

Pero en algunos casos puede que esta solución no funcione. si no es trabajo puedes usar mi segunda solución.

Solución 2:

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

Suponemos que list1 es su lista principal y list2 es su segunda lista y desea obtener elementos de list1 sin elementos de list2.

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

Como Exceptno modifica la lista, puede usar ForEach en List<T>:

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

Puede que no sea la forma más eficiente, pero es simple, por lo tanto legible, y actualiza la lista original (que es mi requisito).

Necriis
fuente
-3

Aqui tienes..

    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
fuente
3
-1. Esto arrojará una excepción después de que se elimine el primer elemento. Además, es (generalmente) una mejor idea recorrer la lista para eliminarla , ya que generalmente es más pequeña. También está obligando a más recorridos de listas haciéndolo de esta manera.
Adam Robinson