Diferencia entre dos listas


118

Tengo dos listas genéricas llenas de CustomsObjects.

Necesito recuperar la diferencia entre esas dos listas (elementos que están en la primera sin los elementos de la segunda) en una tercera.

Estaba pensando que usarlo .Except()era una buena idea, pero no veo cómo usar esto ... ¡Ayuda!

c# 

Respuestas:


237

Usar Exceptes exactamente la forma correcta de hacerlo. Si su tipo anula Equalsy GetHashCode, o solo está interesado en la igualdad de tipos de referencia (es decir, dos referencias solo son "iguales" si se refieren al mismo objeto exacto), puede usar:

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

Si necesita expresar una idea personalizada de igualdad, por ejemplo, mediante ID, deberá implementar IEqualityComparer<T>. Por ejemplo:

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

Entonces usa:

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

Tenga en cuenta que esto eliminará cualquier elemento duplicado. Si necesita conservar los duplicados, probablemente sería más fácil crear un conjunto list2y usar algo como:

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

18
Solo como un aparte, agregaría que Exceptes una operación Set , luego la lista resultante tendrá valores distintos, por ejemplo, {'A','A','B','C'}.Except({'B','C'}) devuelve{'A'}
digEmAll

4
Tengo esto: {'A', 'A', 'B', 'C'}. Excepto ({'B', 'C'}) devuelve {'A'} que funciona ... sin embargo {'B' , 'C'}. Excepto ({'A', 'B', 'C'}) NO devuelve {'A'}
MrLister

2
La diferencia de conjuntos de dos conjuntos se define como los miembros del primer conjunto que no aparecen en el segundo conjunto (citando MS). Entonces, {B, C}. Excepto ({A, B, C}) no debería devolver nada ya que B y C están en el segundo conjunto. No es un error, más que ver con la definición matemática de la función.
Steve Hibbert

Entonces @JonSkeet, si quiero comparar dos listas sobre la base de digamos 2 propiedades, puedo escribir de esta manera un comparador y obtener los elementos que no son iguales, ¿verdad?
Ehsan Sajjad

1
@NetMage: El OP declaró que quieren "Elementos que están en el primero sin los elementos del segundo", eso me suena a una diferencia establecida. Si la primera lista contiene {5, 5, 5, 5, 1} y la segunda lista contiene {5}, entonces solo 1 está en la primera lista pero no en la segunda.
Jon Skeet

73

Podrías hacer algo como esto:

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

me salvó un bucle para cada bucle.
alice7

12
¿No debería uno de esos "l.alguna propiedad" ser "p.alguna propiedad"?
Manos Dilaverakis

6
Esto se puede simplificar con customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 eso es incorrecto. El Anydebe ser un Allsi desea hacerlo así. Esto se debe a que la segunda lista podría tener el elemento de la primera lista, pero si no es el primero que se verifica, inmediatamente obtendrá un verdadero p.someproperty != l.someproperty. Esto da como resultado que se devuelvan elementos que existen en ambas listas. Lástima de las 6 personas que votaron a favor de esto.
Murphybro2

26

Creo que es importante enfatizar: el uso del método Except le devolverá los artículos que están en el primero sin los artículos del segundo solamente. No devuelve aquellos elementos en segundo que no aparecen en primero.

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

Pero si desea obtener una diferencia real entre dos listas:

Elementos que están en el primero sin los elementos del segundo y elementos que están en el segundo sin los elementos del primero.

Necesita usar Excepto dos veces:

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

O puede utilizar SymmetricExceptWith método de HashSet. Pero cambia el conjunto en el que llamó:

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

Eso es útil. Solo tenga en cuenta que el OP dejó en claro que no querían una diferencia completa: "Necesito recuperar la diferencia entre esas dos listas ( elementos que están en la primera sin los elementos en la segunda )".
rsenna

10
var third = first.Except(second);

(también puede llamar ToList()después Except(), si no le gusta hacer referencia a colecciones perezosas).

El Except()método compara los valores utilizando el comparador predeterminado, si los valores comparados son de tipos de datos base, tal como int, string, decimaletc.

De lo contrario, la comparación se realizará por dirección de objeto, que probablemente no sea lo que desea ... En ese caso, haga que sus objetos personalizados se implementen IComparable(o implemente un personalizado IEqualityComparery páselo al Except()método).


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

Nota: list3contendrá los elementos u objetos que no están en ambas listas. Nota: ToList()no estoList()


1

Dado que el método de extensión Except opera en dos IEumerables, me parece que será una operación O (n ^ 2). Si el rendimiento es un problema (si dice que sus listas son grandes), le sugiero que cree un HashSet de list1 y use el método ExceptWith de HashSet.


9
Enumerable.Exceptutiliza internamente HashSeto algo similar. Definitivamente no usa el algoritmo ingenuo O (n ^ 2).
Jim Mischel

@Jim Mischel: Tienes razón, Enumerable.Except usa una estructura de datos interna de Set y agrega elementos de ambos IEnumerables al Set. Ojalá la documentación dijera algo sobre eso.
foson

1

aquí está mi solución:

    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

un poco tarde, pero aquí hay una solución que funciona para mí

 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

En el código de mención anterior, obtengo la diferencia de propiedades entre mi clase base y la lista de clases derivadas


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

Nota: Lista de resultados son los elementos que existen en la lista de verificación pero no en 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 años después de la respuesta aceptada y no agrega nada nuevo de valor
Mitch Wheat

-3

Si ambas listas implementan la interfaz IEnumerable, puede lograr esto usando LINQ.

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

6
Y si cada una de sus listas contiene un millón de elementos, tendrá que esperar mucho tiempo. Úselo en su IEnumerable.Exceptlugar.
Jim Mischel

3
Sí. Solo tengo unos 450 artículos y todavía estoy esperando. Tenga cuidado al usar esto.
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);
            }
        }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.