Diferença entre duas listas


118

Tenho duas listas genéricas preenchidas com CustomsObjects.

Preciso recuperar a diferença entre essas duas listas (itens que estão na primeira sem os itens da segunda) em uma terceira.

Estava pensando que usar .Except()era uma boa ideia, mas não vejo como usar isso .. Socorro!

c# 

Respostas:


237

Usar Excepté exatamente o caminho certo a seguir. Se o seu tipo se sobrepõe Equalse GetHashCode, ou você está interessado apenas na igualdade de tipo de referência (ou seja, duas referências são apenas "iguais" se se referem ao mesmo objeto), você pode apenas usar:

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

Se você precisa expressar uma ideia personalizada de igualdade, por exemplo, por ID, você precisará implementar IEqualityComparer<T>. Por exemplo:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Então use:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Observe que isso removerá quaisquer elementos duplicados. Se você precisa que as duplicatas sejam preservadas, provavelmente seria mais fácil criar um conjunto list2e usar algo como:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

18
Como um aparte, eu adicionaria que Excepté uma operação Set , então a lista resultante terá valores distintos, por exemplo, {'A','A','B','C'}.Except({'B','C'}) retorna{'A'}
digEmAll

4
Eu tenho isto: {'A', 'A', 'B', 'C'}. Exceto ({'B', 'C'}) retorna {'A'} que funciona ... entretanto {'B' , 'C'}. Exceto ({'A', 'B', 'C'}) NÃO retorna {'A'}
MrLister

2
A diferença de conjunto de dois conjuntos é definida como os membros do primeiro conjunto que não aparecem no segundo conjunto (citando MS). Portanto, {B, C}. Exceto ({A, B, C}) não deve retornar nada, pois B e C estão no segundo conjunto. Não é um bug, tem mais a ver com a definição matemática da função.
Steve Hibbert de

Então, @JonSkeet, se eu quiser comparar duas listas com base em, digamos, 2 propriedades, posso escrever este comparador de forma e obter os itens que não são iguais, certo?
Ehsan Sajjad

1
@NetMage: O OP declarou que quer "itens que estão no primeiro sem os itens do segundo" - isso soa como uma diferença definida para mim. Se a primeira lista contém {5, 5, 5, 5, 1} e a segunda lista contém {5}, então apenas 1 está na primeira lista, mas não na segunda.
Jon Skeet

72

Você poderia fazer algo assim:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

isso me salvou um loop foreach.
alice7,

12
Um desses "l.someproperty" não deveria ser "p.someproperty"?
Manos Dilaverakis

6
Isso pode ser simplificado com customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 isso está incorreto. O Anydeve ser um Allse você quiser fazê-lo assim. Isso ocorre porque a segunda lista poderia ter o item da primeira lista, mas se não for o primeiro verificado, obterá imediatamente um verdadeiro para p.someproperty != l.someproperty. Isso resulta na devolução de itens que existem em ambas as listas. Que vergonha para as 6 pessoas que votaram a favor.
Murphybro2

26

Acho importante enfatizar - usar o método Exceto retornará itens que estão no primeiro sem os itens do segundo apenas. Ele não retorna aqueles elementos em segundo que não aparecem em primeiro.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

Mas se você quiser obter uma diferença real entre duas listas:

Itens que estão no primeiro sem os itens do segundo e itens que estão no segundo sem os itens do primeiro.

Você precisa usar Exceto duas vezes:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Ou você pode usar o método SymmetricExceptWith de HashSet. Mas isso muda o conjunto em que chamou:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

Isso é útil. Esteja ciente de que o OP deixou claro que eles não queriam diferença total: "Preciso recuperar a diferença entre essas duas listas ( itens que estão na primeira sem os itens da segunda )".
rsenna de

10
var third = first.Except(second);

(você também pode ligar ToList()depois Except(), se não gosta de fazer referência a coleções preguiçosas.)

O Except()método compara os valores usando o comparador padrão, se os valores a serem comparadas estão de tipos de dados de base, tais como int, string, decimaletc.

Caso contrário, a comparação será feita pelo endereço do objeto, que provavelmente não é o que você deseja ... Nesse caso, faça com que seus objetos personalizados implementem IComparable(ou implemente um personalizado IEqualityComparere passe para o Except()método).


3
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Nota: list3conterá os itens ou objetos que não estão em ambas as listas. Nota: ToList()não étoList()


1

Como o método de extensão Exceto opera em dois IEumerables, parece-me que será uma operação O (n ^ 2). Se o desempenho for um problema (se digamos que suas listas sejam grandes), sugiro criar um HashSet a partir da lista1 e usar o método ExceptWith do HashSet.


9
Enumerable.Exceptusa internamente HashSetou algo semelhante. Definitivamente, não usa o algoritmo ingênuo O (n ^ 2).
Jim Mischel,

@Jim Mischel: Você está certo, Enumerable.Except usa uma estrutura de dados Set interna e adiciona itens de ambos IEnumerables ao Set. Gostaria que a documentação dissesse algo sobre isso.
foson

1

aqui está minha solução:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

um pouco tarde, mas aqui está a solução de trabalho para mim

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

No código de menção acima, estou obtendo a diferença de propriedades entre minha classe base e a lista de classes derivadas


-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

Nota: Lista de resultados são os itens que existem na lista de verificação, mas não em myList
Teezy7

-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

6 anos após a resposta aceita e não acrescenta nada de novo de valor
Mitch Wheat

-3

Se ambas as listas implementarem a interface IEnumerable, você poderá fazer isso usando LINQ.

list3 = list1.where(i => !list2.contains(i));

6
E se cada uma de suas listas contiver um milhão de itens, você vai esperar muito tempo. Use em seu IEnumerable.Exceptlugar.
Jim Mischel,

3
Sim. Tenho apenas cerca de 450 itens e ainda estou esperando. Tenha cuidado ao usar isso.
Marnee KG7SIO

-3
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.