Różnica między dwoma listami


118

Mam dwie ogólne listy wypełnione CustomsObjects.

Muszę znaleźć różnicę między tymi dwiema listami (pozycje, które są na pierwszej bez pozycji na drugiej) w trzeciej.

Myślałem, że używanie .Except()to dobry pomysł, ale nie wiem, jak to wykorzystać. Pomocy!

c# 

Odpowiedzi:


237

Używanie Exceptjest dokładnie właściwą drogą. Jeśli Twój typ zastępuje Equalsi GetHashCodelub interesuje Cię tylko równość typów referencyjnych (tj. Dwie referencje są „równe” tylko wtedy, gdy odnoszą się do dokładnie tego samego obiektu), możesz po prostu użyć:

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

Jeśli chcesz wyrazić własnego ideę równości, np ID, trzeba wdrożyć IEqualityComparer<T>. Na przykład:

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

Następnie użyj:

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

Pamiętaj, że spowoduje to usunięcie wszelkich zduplikowanych elementów. Jeśli chcesz zachować duplikaty, prawdopodobnie najłatwiej byłoby utworzyć zestaw z list2i użyć czegoś takiego:

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

18
Tak jak na bok, bym dodać, że Exceptjest to zestaw operacji, a następnie otrzymany lista będzie mieć różne wartości, np {'A','A','B','C'}.Except({'B','C'}) zostaje przywrócone{'A'}
digEmAll

4
Mam to: {'A', 'A', 'B', 'C'}. Z wyjątkiem ({'B', 'C'}) zwraca {'A'}, które działa ... jednak {'B' , 'C'}. Z wyjątkiem ({'A', 'B', 'C'}) NIE zwraca {'A'}
MrLister

2
Różnica między dwoma zbiorami jest definiowana jako elementy pierwszego zestawu, które nie pojawiają się w drugim zestawie (cytując MS). Zatem {B, C} .Except ({A, B, C}) nie powinno zwracać nic, ponieważ B i C są oba w drugim zbiorze. To nie błąd, a raczej matematyczna definicja funkcji.
Steve Hibbert

Więc @JonSkeet, jeśli chcę porównać dwie listy na podstawie powiedzmy 2 właściwości, mogę napisać w ten sposób porównujący i uzyskać elementy, które nie są równe, prawda?
Ehsan Sajjad

1
@NetMage: OP stwierdził, że chce „Przedmioty, które są na pierwszym miejscu, bez elementów w drugim” - to brzmi dla mnie jak różnica w zestawie. Jeśli pierwsza lista zawiera {5, 5, 5, 5, 1}, a druga lista zawiera {5}, to tylko 1 znajduje się na pierwszej liście, ale nie na drugiej.
Jon Skeet,

73

Możesz zrobić coś takiego:

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

zaoszczędziło mi to jedną pętlę dla każdego.
alice7

12
Czy jedna z tych „l.someproperty” nie powinna być „p.someproperty”?
Manos Dilaverakis

6
Można to uprościć za pomocą customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 to nieprawidłowe. AnyPowinno być All, jeśli chcesz to zrobić tak. Dzieje się tak, ponieważ druga lista może zawierać element z pierwszej listy, ale jeśli nie jest sprawdzona jako pierwsza, natychmiast otrzyma wartość true dla p.someproperty != l.someproperty. Powoduje to zwrócenie elementów, które istnieją na obu listach. Wstyd dla 6 osób, które to poparły.
Murphybro2

26

Myślę, że ważne, aby podkreślić - użycie metody Except zwróci tylko przedmioty, które są na pierwszym miejscu, bez elementów w drugim. Nie zwraca tych elementów w drugiej kolejności, które nie pojawiają się w pierwszej.

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

Ale jeśli chcesz uzyskać prawdziwą różnicę między dwiema listami:

Elementy, które są na pierwszym miejscu bez elementów w drugim i elementy, które są na drugim miejscu bez elementów w pierwszym.

Musisz dwukrotnie użyć wyjątku:

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

Lub możesz użyć metody SymmetricExceptWith HashSet. Ale zmienia zestaw, na którym wywołano:

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

To jest pomocne. Należy tylko pamiętać, że OP jasno określił, że nie chce pełnej różnicy: „Muszę pobrać różnicę między tymi dwiema listami ( pozycje, które są na pierwszej, bez pozycji na drugiej )”.
rsenna

10
var third = first.Except(second);

(możesz również zadzwonić ToList()po Except(), jeśli nie lubisz odwoływać się do leniwych kolekcji).

Except()Sposób porównuje wartości zmierzone z domyślną comparer, gdy wartości porównywane są z typów danych zasady, takiej jak int, string, decimaletc.

W przeciwnym razie porównanie zostanie dokonane na podstawie adresu obiektu, który prawdopodobnie nie jest tym, czego chcesz ... W takim przypadku zaimplementuj własne obiekty IComparable(lub zaimplementuj własne IEqualityCompareri przekaż je do Except()metody).


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

Uwaga: list3będzie zawierał elementy lub obiekty, których nie ma na obu listach. Uwaga: ToList()nietoList()


1

Ponieważ metoda rozszerzenia Except działa na dwóch IEumerables, wydaje mi się, że będzie to operacja O (n ^ 2). Jeśli wydajność jest problemem (jeśli mówisz, że twoje listy są duże), sugerowałbym utworzenie HashSet na podstawie listy1 i użycie metody HashSet's ExceptWith.


9
Enumerable.Exceptużywa wewnętrznie HashSetlub coś podobnego. Zdecydowanie nie używa naiwnego algorytmu O (n ^ 2).
Jim Mischel,

@Jim Mischel: Masz rację, Enumerable.Except używa wewnętrznej struktury danych Set i dodaje elementy z obu IEnumerables do zestawu. Chciałbym, żeby dokumentacja coś o tym mówiła.
foson

1

oto moje rozwiązanie:

    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

trochę późno, ale tutaj jest dla mnie działające rozwiązanie

 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

W powyższym kodzie otrzymuję różnicę właściwości między moją klasą bazową a listą klas pochodnych


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

Uwaga: Lista wyników to pozycje, które istnieją na liście kontrolnej, ale nie znajdują się na
mojej liście

-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 lat po zaakceptowaniu odpowiedzi i nie dodaje nic wartościowego
Mitch Wheat

-3

Jeśli obie listy implementują interfejs IEnumerable, możesz to osiągnąć za pomocą LINQ.

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

6
A jeśli każda z twoich list zawiera milion pozycji, będziesz czekać długo. Użyj IEnumerable.Exceptzamiast tego.
Jim Mischel

3
Tak. Mam tylko około 450 pozycji i nadal czekam. Uważaj, używając tego.
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);
            }
        }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.