Remover itens de uma lista em outra

206

Estou tentando descobrir como percorrer uma lista genérica de itens que quero remover de outra lista de itens.

Então, digamos que eu tenho isso como um exemplo hipotético

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

Desejo percorrer a lista1 com um foreach e remover cada item da lista1, que também está contido na lista2.

Não sei bem como proceder, pois o foreach não é baseado em índices.

PositiveGuy
fonte
1
Deseja remover itens da Lista1 que também estão na Lista2?
precisa saber é o seguinte
1
O que deve acontecer se você tiver list1 = {foo1} e list2 = {foo1, foo1}. Todas as cópias de foo1 devem ser removidas da lista2 ou apenas a primeira?
precisa
2
-1 - Eu diminuí a votação de todas as respostas desta pergunta porque achei que todas estavam erradas, mas parece que a pergunta foi feita de maneira horrível. Agora, não posso mudá-los - desculpas. Deseja remover os itens list1existentes em list2ou deseja remover os itens list2existentes list1? No momento deste comentário, cada resposta fornecida executará a última.
quer
7
@ John Rashch, você deve estar um pouco menos feliz com esses votos negativos. Algumas das respostas são bastante conceituais e demonstram apenas como alcançar o que o OP deseja, mesmo sem se relacionar com as listas mencionadas na pergunta.
João Angelo
3
@ Mark - você está certo, a culpa é minha - é por isso que eu coloquei o comentário aqui explicando o que aconteceu, eu estava procurando por uma resposta anterior. comentários depois que eu encontrei - acontece que não é o melhor processo para isso!
John Rasch

Respostas:

358

Você pode usar Exceto :

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

Você provavelmente nem precisa dessas variáveis ​​temporárias:

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

Observe que Exceptnão modifica nenhuma das listas - ele cria uma nova lista com o resultado.

Mark Byers
fonte
13
Ponto menor, mas isso produzirá um IEnumerable<car>, não um List<car>. Você precisa ligar ToList()para obter uma lista de volta. Além disso, acredito que deveria serGetSomeOtherList().Except(GetTheList()).ToList()
Adam Robinson
9
Você também precisará using System.Linq;se não o tiver antes.
Yellavon
1
Nota: list1.Except (list2) não dará o mesmo resultado que list2.Except (list1). O último funcionou para mim.
radbyx
2
Apenas tome cuidado ao usar, Exceptpois isso realmente executa uma operação definida , que distingue a lista resultante. Eu não estava esperando esse comportamento, pois estou usando a List, não a HashSet. Relacionado.
Logan #
4
Como é que esta é a resposta certa? Claro que isso pode lhe dar o que você deseja no seu contexto, no entanto, "Remover itens de uma lista em outra" certamente não é equivalente a uma operação de diferença definida e você não deve desinformar as pessoas, aceitando isso como a resposta certa !!!!
User1935724
37

Você não precisa de um índice, pois a List<T>classe permite remover itens por valor, em vez de indexar usando a Removefunção

foreach(car item in list1) list2.Remove(item);
Adam Robinson
fonte
3
+1, mas na IMO você deve usar colchetes na list2.Remove(item);declaração.
ANeves
2
@sr pt: Eu sempre uso colchetes em declarações que aparecem em outra linha, mas não em blocos de declaração única que posso / coloco na mesma linha que a declaração de controle de fluxo.
Adam Robinson
4
@uriz: desconsiderando as qualificações do que seria elegante, esta é a única resposta que realmente faz o que a pergunta diz (remove os itens da lista principal); a outra resposta cria uma nova lista, que pode não ser desejável se a lista estiver sendo transmitida por outro chamador que espera que seja modificado em vez de obter uma lista de substituição.
Adam Robinson
5
@uriz @AdamRobinson já que estamos discutindo soluções elegantes ...list1.ForEach(c => list2.Remove(c));
David Sherret
1
"elegante" deve significar "o desenvolvedor preso em manter esse código achará simples e fácil de entender", razão pela qual esta é a melhor resposta.
Seth
22

Eu recomendaria o uso dos métodos de extensão LINQ . Você pode fazer isso facilmente com uma linha de código da seguinte maneira:

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

Isso supõe, é claro, que os objetos na lista1 que você está removendo da lista2 sejam da mesma instância.

Berkshire
fonte
2
Ele remove duplicatas também.
JulyOrdinary ordinário
17

No meu caso, eu tinha duas listas diferentes, com um identificador comum, como uma chave estrangeira. A segunda solução citada por "nzrytmn" :

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

Foi o que melhor se encaixou na minha situação. Eu precisava carregar um DropDownList sem os registros que já haviam sido registrados.

Obrigado !!!

Este é o meu 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
fonte
OU nunca explicou o que DDlT3 foi
rogue39nin
15

Você poderia usar o LINQ, mas eu usaria o RemoveAllmétodo Eu acho que é o que melhor expressa sua intenção.

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
fonte
9
Ou ainda mais simples com grupos de métodos que você pode fazer - inteiros.RemoveAll (remove.Contains);
21713 Ryan
12
list1.RemoveAll(l => list2.Contains(l));
Alexandre Amado de Castro
fonte
aka "totalmente impuro" :-)
Xan-Kun Clark-Davis
O que há de errado com isso. Parece melhor do que criar outra lista usando Except. Especialmente quando as duas listas são muito pequenas.
Mike Keskinov
1
Como os dois métodos de lista são O(N), isso levará a O(N^2)que pode haver um problema com listas grandes.
Tigrou
7

Solução 1: Você pode fazer assim:

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

Mas, em alguns casos, essa solução pode não funcionar. se não funcionar, você pode usar minha segunda solução.

Solução 2:

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

fingimos que list1 é sua lista principal e list2 é sua lista secundária e você deseja obter itens da lista1 sem itens da lista2.

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

Como Exceptnão modifica a lista, você pode usar o ForEach em List<T>:

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

Pode não ser a maneira mais eficiente, mas é simples, portanto legível, e atualiza a lista original (que é meu requisito).

Necriis
fonte
-3

Aqui você vai ..

    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
fonte
3
-1. Isso gerará uma exceção após a remoção do primeiro item. Além disso, é (geralmente) uma idéia melhor percorrer a lista para remover , já que geralmente é menor. Você também está forçando mais percursos de lista fazendo dessa maneira.
Adam Robinson