Najszybszy sposób na porównanie dwóch ogólnych list różnic

214

Co jest najszybsze (i najmniej wymagające zasobów) do porównania dwóch masywnych (> 50 000 pozycji), w wyniku czego mają dwie listy takie jak te poniżej:

  1. elementy, które pojawiają się na pierwszej liście, ale nie na drugiej
  2. elementy, które pojawiają się na drugiej liście, ale nie na pierwszej

Obecnie pracuję z List lub IReadOnlyCollection i rozwiązuję ten problem w zapytaniu linq:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

Ale to nie działa tak dobrze, jak bym chciał. Masz pomysł, aby uczynić to szybszym i mniej wymagającym zasobów, ponieważ muszę przetworzyć wiele list?

Szczery
źródło

Odpowiedzi:

454

Użyj Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

Podejrzewam, że istnieją podejścia, które faktycznie byłyby nieznacznie szybsze niż to, ale nawet to będzie znacznie szybsze niż twoje podejście O (N * M).

Jeśli chcesz je połączyć, możesz utworzyć metodę z powyższym, a następnie instrukcję return:

return !firstNotSecond.Any() && !secondNotFirst.Any();

Jedna uwaga jest taka, że nie ma różnicy w wynikach między oryginalnego kodu w pytaniu a tu rozwiązanie: zduplikowane elementy, które są tylko w jednej listy będą przekazywane tylko raz z mojego kodu, podczas gdy oni być zgłaszane tyle czasy występujące w oryginalnym kodzie.

Na przykład z listami [1, 2, 2, 2, 3]i [1]„elementy na liście 1, ale nie na liście 2” dają oryginalny kod [2, 2, 2, 3]. Z moim kodem byłoby po prostu [2, 3]. W wielu przypadkach nie będzie to problemem, ale warto o tym wiedzieć.

Jon Skeet
źródło
8
To naprawdę ogromny wzrost wydajności! Dziękuję za tę odpowiedź.
Frank
2
Zastanawiam się nad dwiema dużymi listami, czy warto posortować przed porównaniem? lub wewnątrz Z wyjątkiem metody rozszerzenia, przekazana lista jest już posortowana.
Larry,
9
@ Larry: Nie jest posortowane; buduje zestaw skrótów.
Jon Skeet,
2
@PranavSingh: Będzie działać na wszystko, co ma odpowiednią równość - więc jeśli Twój typ niestandardowy zastąpi Equals(object)i / lub zaimplementuje IEquatable<T>, powinno być w porządku.
Jon Skeet,
2
@ k2ibegin: Wykorzystuje domyślny moduł porównujący równość, który użyje IEquatable<T>implementacji lub object.Equals(object)metody. Wygląda na to, że powinieneś utworzyć nowe pytanie z minimalnym, powtarzalnym przykładem - nie możemy tak naprawdę zdiagnozować rzeczy w komentarzach.
Jon Skeet,
40

Bardziej wydajne byłoby użycie Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

Ta metoda jest implementowana przy użyciu odroczonego wykonania. Oznacza to, że możesz napisać na przykład:

var first10 = inListButNotInList2.Take(10);

Jest również wydajny, ponieważ wewnętrznie używa a Set<T>do porównywania obiektów. Działa najpierw poprzez zebranie wszystkich różnych wartości z drugiej sekwencji, a następnie przesłanie strumieniowe wyników pierwszej, sprawdzając, czy nie były wcześniej widoczne.

Tim Schmelter
źródło
1
Hmm Niezupełnie odroczony. Powiedziałbym częściowo odroczony. Całość Set<T>jest budowana z drugiej sekwencji (tzn. Jest w pełni iterowana i przechowywana), a następnie dostarczane są elementy, które można dodać z pierwszej sekwencji.
spędza
2
@spender, to tak, jakby powiedzieć, że wykonanie Wherejest częściowo odroczone, ponieważ w list.Where(x => x.Id == 5)wartości liczby 5jest przechowywany na początku, a nie leniwie.
jwg
27

Enumerable.SequenceEqual Metoda

Określa, czy dwie sekwencje są równe zgodnie z urządzeniem do porównywania równości. MS.Docs

Enumerable.SequenceEqual(list1, list2);

Działa to dla wszystkich pierwotnych typów danych. Jeśli chcesz użyć go na niestandardowych obiektach, musisz je zaimplementowaćIEqualityComparer

Definiuje metody wspomagające porównywanie obiektów pod kątem równości.

Interfejs IEqualityComparer

Definiuje metody wspomagające porównywanie obiektów pod kątem równości. MS.Docs dla IEqualityComparer

miguelmpn
źródło
to powinna być zaakceptowana odpowiedź. Pytanie nie dotyczy zestawów, ale zestawień, które mogą zawierać duplikaty elementów.
Adrian Nasui,
3
Nie rozumiem, jak to może być odpowiedź, biorąc pod uwagę, że wynik SequenceEqualjest prosty bool. OP chce dwóch list wyników - i opisuje, czego chcą w zakresie ustawianych operacji: „elementy, które pojawiają się na pierwszej liście, ale nie na drugiej”. Nie ma żadnych oznak, że kolejność jest istotna, natomiast SequenceEqual nie uznają to za stosowne. To wydaje się odpowiadać na zupełnie inne pytanie.
Jon Skeet,
tak, poprawne, wygląda na to, że odpowiedziałem na to zbyt szybko i nie spojrzałem na drugą część wniosku ... tak samo jak na pierwsze dwa komentarze ...
miguelmpn
9

Jeśli chcesz, aby wyniki nie uwzględniały wielkości liter , następujące funkcje będą działać:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondzawierałby b1.dll

secondNotFirstzawierałby b2.dll

np
źródło
5

Nie dla tego problemu, ale oto kod do porównania list na równe i nie! identyczne przedmioty:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}
Pius Hermit
źródło
1
Właśnie tego potrzebujesz, aby móc porównać niestandardowe typy danych. Następnie użyjExcept
Pranav Singh
Prawdopodobnie można to zrobić lepiej z typami sortowalnymi. Działa to w O (n ^ 2), podczas gdy możesz zrobić O (nlogn).
yuvalm2
3

spróbuj w ten sposób:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
Ali Issa
źródło
13
To cierpi z powodu okropnej wydajności, wymagającej skanowania drugiej listy dla każdego elementu w pierwszym. Nie przegłosowywanie, ponieważ działa, ale jest tak złe, jak oryginalny kod.
spędza
3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

Czasami musisz tylko wiedzieć, czy dwie listy są różne, a nie jakie są te różnice. W takim przypadku rozważ dodanie tej metody rozszerzenia do swojego projektu. Zauważ, że wymienione obiekty powinny implementować IEquatable!

Stosowanie:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Niezależnie od Componentklasy, przedstawione tutaj metody Carpowinny być implementowane prawie identycznie.

Bardzo ważne jest, aby zauważyć, jak napisaliśmy GetHashCode. W celu prawidłowego wdrożenia IEquatable, Equalsi GetHashCode moszczu działać na właściwości instancji w logicznie zgodny sposób.

Dwie listy o tej samej zawartości są wciąż różnymi obiektami i będą generować różne kody skrótu. Ponieważ chcemy, aby te dwie listy były traktowane jako równe, musimy pozwolić na GetHashCodewygenerowanie tej samej wartości dla każdej z nich. Możemy to osiągnąć, delegując kod skrótu do każdego elementu na liście i używając standardowego bitowego XOR, aby połączyć je wszystkie. XOR jest niezależny od kolejności, więc nie ma znaczenia, czy listy są sortowane inaczej. Liczy się tylko to, że zawierają tylko równoważnych członków.

Uwaga: dziwna nazwa ma sugerować fakt, że metoda nie uwzględnia kolejności elementów na liście. Jeśli zależy Ci na kolejności elementów na liście, ta metoda nie jest dla Ciebie!

Devon Parsons
źródło
1

Użyłem tego kodu do porównania dwóch list, które mają milion rekordów.

Ta metoda nie zajmie dużo czasu

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }
Satish
źródło
0

Jeśli potrzebny będzie tylko łączony wynik, to również zadziała:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

gdzie T jest rodzajem elementu listy.

Ali Khaleghi Karsalari
źródło
-1

Może to jest śmieszne, ale działa dla mnie

string.Join ("", List1)! = string.Join ("", List2)

Jibz
źródło
ponieważ jest tutaj napisane, nie działałoby nawet dla List <ciąg> lub Lista <int>, ponieważ na przykład dwie listy 11; 2; 3 i 1; 12; 3 byłyby identyczne, ponieważ nie łączysz ciągów z niektórymi unikalny separator, który nie jest możliwym elementem na liście. Poza tym łączenie łańcuchów dla listy zawierającej wiele elementów jest prawdopodobnie zabójcą wydajności.
SwissCoder,
@SwissCoder: Mylisz się, to nie jest zabójca performacne dla łańcucha. Jeśli masz dwie listy zawierające 50 000 ciągów (każdy o długości 3), ten algorytm potrzebuje 3 ms na moim komputerze. Akceptowane odpowiedzi wymagają 7. Myślę, że sztuczka polega na tym, że Jibz potrzebuje tylko jednego porównania ciągów. Oczywiście musi dodać unikalny separator.
user1027167
@ user1027167: Nie mówię o bezpośrednim porównywaniu ciągów (ponieważ to też nie jest pytanie). Wywołanie metody .ToString () wszystkich obiektów na liście zawierającej 50 000 obiektów może stworzyć ogromny ciąg, w zależności od tego, jak zostanie zaimplementowany. Nie sądzę, żeby tak było. W takim przypadku ryzykowne jest poleganie na tym, że znak lub ciąg znaków są „unikalne”, a kod tak naprawdę nie nadawałby się do wielokrotnego użytku.
SwissCoder
Ok, to prawda. Pytający zapytał o najszybszy sposób bez podawania typu danych swoich list. Prawdopodobnie ta odpowiedź jest najszybszym sposobem na użycie pytającego.
user1027167
-3

Myślę, że jest to prosty i łatwy sposób na porównanie dwóch list element po elemencie

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)
użytkownik10915707
źródło
3
To jest pytanie C # i nie podałeś kodu C #.
Wai Ha Lee
1
Być może mógłbyś usunąć tę odpowiedź i przenieść ją (na przykład) Jak mogę porównać dwie listy w pythonie i zwrócić dopasowania ?
Wai Ha Lee
-4

To najlepsze rozwiązanie, jakie znajdziesz

var list3 = list1.Where(l => list2.ToList().Contains(l));
Fajoui El Mahdi
źródło
1
To jest naprawdę bardzo złe, ponieważ tworzy nowe List<T>dla każdego elementu w list1. Również wynik jest wywoływany, list3gdy nie jest to List<T>.
Wai Ha Lee