Differenza tra due elenchi


118

Ho due elenchi generici riempiti con CustomsObjects.

Ho bisogno di recuperare la differenza tra questi due elenchi (elementi che sono nel primo senza gli elementi nel secondo) in un terzo.

Pensavo che usare .Except()fosse una buona idea ma non vedo come usarlo .. Aiuto!

c# 

Risposte:


237

L'utilizzo Exceptè esattamente la strada giusta da percorrere. Se il tuo tipo sostituisce Equalse GetHashCode, o sei interessato solo all'uguaglianza del tipo di riferimento (ovvero due riferimenti sono "uguali" solo se si riferiscono allo stesso identico oggetto), puoi semplicemente usare:

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

Se hai bisogno di esprimere un'idea personalizzata di uguaglianza, ad esempio per ID, dovrai implementarla IEqualityComparer<T>. Per esempio:

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

Quindi usa:

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

Nota che questo rimuoverà tutti gli elementi duplicati. Se hai bisogno di duplicare da conservare, probabilmente sarebbe più facile creare un set da list2e usare qualcosa come:

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

18
Per inciso, aggiungerei che Exceptè un'operazione di impostazione , quindi l'elenco risultante avrà valori distinti, ad esempio {'A','A','B','C'}.Except({'B','C'}) ritorni{'A'}
digEmAll

4
Ho questo: {'A', 'A', 'B', 'C'}. Ad eccezione di ({'B', 'C'}) restituisce {'A'} che funziona ... tuttavia {'B' , 'C'}. Tranne ({'A', 'B', 'C'}) NON restituisce {'A'}
MrLister

2
La differenza di set di due set è definita come i membri del primo set che non appaiono nel secondo set (citando MS). Quindi, {B, C}. Tranne ({A, B, C}) non dovrebbe restituire nulla poiché B e C sono entrambi nel secondo set. Non è un bug, ha più a che fare con la definizione matematica della funzione.
Steve Hibbert

Quindi @ JonSkeet se voglio confrontare due elenchi sulla base di diciamo 2 proprietà posso scrivere in questo modo comparer e ottenere gli elementi che non sono uguali, giusto?
Ehsan Sajjad

1
@NetMage: L'OP ha dichiarato di volere "Oggetti che sono nel primo senza gli elementi nel secondo" - che suona come una differenza fissa per me. Se il primo elenco contiene {5, 5, 5, 5, 1} e il secondo elenco contiene {5}, solo 1 è nel primo elenco ma non nel secondo.
Jon Skeet

74

Potresti fare qualcosa del genere:

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

mi ha salvato un ciclo foreach.
alice7

12
Una di quelle "l.someproperty" non dovrebbe essere "p.someproperty"?
Manos Dilaverakis

6
Questo può essere semplificato con customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 non è corretto. Il Anydovrebbe essere un Allse si vuole fare in quel modo. Questo perché il secondo elenco potrebbe contenere l'elemento del primo elenco, ma se non è il primo selezionato, otterrà immediatamente un vero per p.someproperty != l.someproperty. Ciò si traduce in elementi restituiti che esistono in entrambi gli elenchi. Peccato per le 6 persone che hanno votato positivamente.
Murphybro2

26

Penso sia importante sottolineare: l'utilizzo del metodo Except restituirà gli elementi che si trovano nel primo senza gli elementi solo nel secondo. Non restituisce quegli elementi in secondo che non appaiono in primo.

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

Ma se vuoi ottenere una reale differenza tra due elenchi:

Elementi che si trovano nel primo senza gli elementi nel secondo e gli elementi che si trovano nel secondo senza gli elementi nel primo.

È necessario utilizzare tranne due volte:

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

Oppure puoi usare il metodo SymmetricExceptWith di HashSet. Ma cambia il set su cui ha chiamato:

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

È utile. Basta essere consapevoli che l'OP ha chiarito che non volevano la differenza completa: "Ho bisogno di recuperare la differenza tra questi due elenchi ( elementi che sono nel primo senza gli elementi nel secondo )".
rsenna

10
var third = first.Except(second);

(puoi anche chiamare ToList()dopoExcept() , se non ti piace fare riferimento a raccolte pigre.)

Il Except()metodo confronta i valori utilizzando l'operatore di confronto predefinito, se i valori confrontati sono di tipi di dati di base, ad esempio int, string, decimaletc.

Altrimenti il ​​confronto verrà effettuato in base all'indirizzo dell'oggetto, che probabilmente non è quello che desideri ... In tal caso, fai implementare i tuoi oggetti personalizzati IComparable(o implementa un custom IEqualityComparere passalo al Except()metodo).


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

Nota: list3conterrà gli elementi o gli oggetti che non sono in entrambi gli elenchi. Nota: ToList()non è cosìtoList()


1

Poiché il metodo di estensione Except opera su due IEumerables, mi sembra che sarà un'operazione O (n ^ 2). Se le prestazioni sono un problema (se diciamo che i tuoi elenchi sono grandi), suggerirei di creare un HashSet da list1 e utilizzare il metodo ExceptWith di HashSet.


9
Enumerable.Exceptusi internamente HashSeto qualcosa di simile. Sicuramente non usa l'algoritmo ingenuo O (n ^ 2).
Jim Mischel

@ Jim Mischel: Hai ragione, Enumerable.Except utilizza una struttura di dati Set interna e aggiunge elementi da entrambi IEnumerables nel Set. Vorrei che la documentazione dicesse qualcosa al riguardo.
foson

1

ecco la mia soluzione:

    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 po 'tardi ma qui sta funzionando per me

 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

Nel codice di cui sopra sto ottenendo la differenza di proprietà tra la mia classe base e l'elenco delle classi derivate


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

Nota: l'elenco dei risultati è costituito dagli elementi presenti nell'elenco di controllo ma non in 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 anni dopo aver accettato la risposta e non aggiunge nulla di nuovo di valore
Mitch Wheat

-3

Se entrambi gli elenchi implementano l'interfaccia IEnumerable, puoi ottenere ciò utilizzando LINQ.

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

6
E se i tuoi elenchi contengono ciascuno un milione di elementi, dovrai aspettare molto tempo. Usa IEnumerable.Exceptinvece.
Jim Mischel

3
Sì. Ho solo circa 450 articoli e sto ancora aspettando. Stai attento a usarlo.
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);
            }
        }
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.