Chciałbym porównać dwie kolekcje (w C #), ale nie jestem pewien, jaki jest najlepszy sposób na wydajne wdrożenie tego.
Przeczytałem inny wątek o Enumerable.SequenceEqual , ale nie jest to dokładnie to, czego szukam.
W moim przypadku dwie kolekcje byłyby równe, gdyby obie zawierały te same elementy (bez względu na kolejność).
Przykład:
collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};
collection1 == collection2; // true
Zwykle robię pętlę przez każdy element jednej kolekcji i sprawdzam, czy istnieje w drugiej kolekcji, a następnie przechodzę w pętli przez każdy element z drugiej kolekcji i sprawdzam, czy istnieje w pierwszej kolekcji. (Zaczynam od porównania długości).
if (collection1.Count != collection2.Count)
return false; // the collections are not equal
foreach (Item item in collection1)
{
if (!collection2.Contains(item))
return false; // the collections are not equal
}
foreach (Item item in collection2)
{
if (!collection1.Contains(item))
return false; // the collections are not equal
}
return true; // the collections are equal
Jednak nie jest to całkowicie poprawne i prawdopodobnie nie jest to najbardziej efektywny sposób porównywania dwóch kolekcji pod kątem równości.
Przykład, o którym mogę pomyśleć, byłby zły:
collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}
Co byłoby równe mojej realizacji. Czy mam po prostu policzyć, ile razy każdy przedmiot został znaleziony i upewnić się, że liczby są równe w obu kolekcjach?
Przykłady są w jakimś języku C # (nazwijmy to pseudo-C #), ale udziel odpowiedzi w dowolnym języku, to nie ma znaczenia.
Uwaga: Użyłem liczb całkowitych w przykładach ze względu na prostotę, ale chcę również móc używać obiektów typu referencyjnego (nie zachowują się one poprawnie jak klucze, ponieważ porównywane jest tylko odniesienie do obiektu, a nie treść).
źródło
Odpowiedzi:
Okazuje się, że Microsoft uwzględnił to już w swojej strukturze testowej: CollectionAssert.AreEquivalent
Używając reflektora, zmodyfikowałem kod za AreEquivalent (), aby utworzyć odpowiednią funkcję porównującą równość. Jest bardziej kompletny niż istniejące odpowiedzi, ponieważ bierze pod uwagę wartości null, implementuje IEqualityComparer i ma pewną wydajność i kontrolę przypadków skrajnych. plus, to Microsoft :)
Przykładowe użycie:
Lub jeśli chcesz bezpośrednio porównać dwie kolekcje:
Na koniec możesz użyć wybranej przez siebie porównywarki równości:
źródło
EqualityComparer
(podanym przez Ciebie lubEqualityComparer.Default
możesz sprawdzić Reflektor lub źródło odniesienia, aby to sprawdzić). To prawda, że jeśli obiekty ulegną zmianie (a konkretnie zmiany kodu skrótu), gdy ta metoda jest uruchomiona, wyniki są nieoczekiwane, ale to po prostu oznacza, że ta metoda nie jest bezpieczna wątkowo w tym kontekście.EqualityComparer
(lubEqualityComparer.Default
jeśli nie określono) i ponownie implementacja jest poprawna.Equals
ze względu naIEqualityComparer<T>
interfejs. To, na co powinieneś spojrzeć, to nazwa samego elementu porównującego . W tym przypadku toMultiSetComparer
ma sens.Prostym i dość wydajnym rozwiązaniem jest posortowanie obu kolekcji, a następnie porównanie ich pod kątem równości:
Ten algorytm to O (N * logN), podczas gdy powyższe rozwiązanie to O (N ^ 2).
Jeśli kolekcje mają określone właściwości, możesz wdrożyć szybsze rozwiązanie. Na przykład, jeśli obie kolekcje są zestawami skrótów, nie mogą zawierać duplikatów. Również sprawdzenie, czy zestaw hash zawiera jakiś element, jest bardzo szybkie. W takim przypadku algorytm podobny do twojego prawdopodobnie byłby najszybszy.
źródło
Utwórz słownik „dict”, a następnie dla każdego członka w pierwszej kolekcji wykonaj dict [member] ++;
Następnie wykonaj pętlę nad drugą kolekcją w ten sam sposób, ale dla każdego elementu członkowskiego wykonaj polecenie [element członkowski] -.
Na koniec obejrzyj wszystkich członków słownika:
Edycja: O ile wiem, jest to w tej samej kolejności, co najbardziej wydajny algorytm. Ten algorytm to O (N), przy założeniu, że Słownik używa wyszukiwań O (1).
źródło
return dict.All(kvp => kvp.Value == 0);
Oto moja (na którą duży wpływ wywarł D.Jennings) generyczna implementacja metody porównania (w C #):
źródło
The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"
- to nie jest prawda. Algorytm opiera się na błędnych założeniach i chociaż działa, jest strasznie nieefektywny.Możesz użyć Hashset . Spójrz na metodę SetEquals .
źródło
Jeśli używasz Shouldly , możesz użyć ShouldAllBe z Contains.
Na koniec możesz napisać rozszerzenie.
AKTUALIZACJA
Opcjonalny parametr istnieje w metodzie ShouldBe .
źródło
bool ignoreOrder
dotyczący metody ShouldBe .EDYCJA: Zdałem sobie sprawę, gdy tylko stwierdziłem, że to naprawdę działa tylko w przypadku zestawów - nie będzie poprawnie radzić sobie z kolekcjami, które mają zduplikowane elementy. Na przykład {1, 1, 2} i {2, 2, 1} będą uważane za równe z punktu widzenia tego algorytmu. Jeśli jednak Twoje kolekcje są zestawami (lub ich równość można zmierzyć w ten sposób), mam nadzieję, że poniższe informacje okażą się przydatne.
Rozwiązanie, którego używam, to:
Linq robi to ze słownika pod okładkami, więc to też jest O (N). (Uwaga: to O (1), jeśli kolekcje nie są tego samego rozmiaru).
Zrobiłem test poczytalności, używając metody „SetEqual” sugerowanej przez Daniela, metody OrderBy / SequenceEquals sugerowanej przez Igora oraz mojej sugestii. Wyniki są poniżej, pokazując O (N * LogN) dla Igora i O (N) dla mojego i Daniela.
Myślę, że prostota kodu przecięcia Linq sprawia, że jest to preferowane rozwiązanie.
źródło
W przypadku braku powtórzeń i kolejności można użyć następującego EqualityComparer, aby zezwolić na kolekcje jako klucze słownikowe:
Oto implementacja ToHashSet (), której użyłem. Algorytm kod hash pochodzi z Effective Java (w drodze Jon Skeet).
źródło
ISet<T>
wyrazić, że jest przeznaczone dla zestawów (tj. bez duplikatów).ISet
tutaj chodziło o potraktowanieIEnumerable
zestawu jako zestawu (bo maszIEnumerable
na początek), choć biorąc pod uwagę 0 głosów za ponad 5 lat to chyba nie był najlepszy pomysł: PRozwiązanie wymaga platformy .NET 3.5 i
System.Collections.Generic
przestrzeni nazw. Według Microsoft ,SymmetricExceptWith
to O (n + m) operacji, z n oznaczająca liczbę elementów w pierwszym zbiorze i m , oznaczającą liczbę elementów na sekundę. W razie potrzeby zawsze można dodać funkcję porównującą równość do tej funkcji.źródło
Dlaczego nie użyć .Except ()
http://msdn.microsoft.com/en-us/library/bb397894.aspx
źródło
Except
nie będzie działać przy liczeniu zduplikowanych elementów. Zwróci prawdę dla zestawów {1,2,2} i {1,1,2}.[1, 1, 2] != [1, 2, 2]
. UżywanieDistinct
sprawi, że będą wyglądać równo.Swojego rodzaju zduplikowany post, ale sprawdź moje rozwiązanie do porównywania kolekcji . To całkiem proste:
Spowoduje to wykonanie porównania równości niezależnie od kolejności:
Spowoduje to sprawdzenie, czy elementy zostały dodane / usunięte:
Spowoduje to zmianę pozycji w słowniku:
Oryginalny post tutaj .
źródło
erickson ma prawie rację: ponieważ chcesz dopasować liczbę duplikatów, potrzebujesz torby . W Javie wygląda to mniej więcej tak:
Jestem pewien, że C # ma wbudowaną implementację zestawu. Najpierw użyłbym tego; jeśli wydajność jest problemem, zawsze możesz użyć innej implementacji zestawu, ale użyć tego samego interfejsu zestawu.
źródło
Oto mój wariant metody rozszerzenia odpowiedzi Ohadsc, na wypadek, gdyby był dla kogoś przydatny
źródło
IEnumerable<T>
są zapytaniami, dzwonienieCount()
nie jest dobrym pomysłem. Oryginalna odpowiedź Ohada polegająca na sprawdzaniu, czy tak jest,ICollection<T>
jest lepszym pomysłem.Oto rozwiązanie, które jest ulepszeniem w stosunku do tego .
źródło
Istnieje wiele rozwiązań tego problemu. Jeśli nie dbasz o duplikaty, nie musisz sortować obu. Najpierw upewnij się, że mają taką samą liczbę elementów. Potem jedna z kolekcji. Następnie binsearch każdy element z drugiej kolekcji w posortowanej kolekcji. Jeśli nie znajdziesz danej pozycji, zatrzymaj się i zwróć false. Złożoność tego: - sortowanie pierwszego zbioru: N Log (N) - przeszukiwanie każdego elementu od drugiego do pierwszego: NLOG (N), więc otrzymujesz 2 * N * LOG (N), zakładając, że pasują i sprawdzasz wszystko. Jest to podobne do złożoności sortowania obu. Daje to również korzyść, jeśli istnieje różnica, jeśli zatrzymasz się wcześniej. Należy jednak pamiętać, że jeśli oba zostaną posortowane przed przejściem do tego porównania i spróbujesz posortować przy użyciu czegoś takiego jak qsort, sortowanie będzie droższe. Istnieją optymalizacje dla tego. Inną alternatywą, która jest świetna w przypadku małych kolekcji, w których znasz zakres elementów, jest użycie indeksu maski bitowej. To da ci wydajność O (n). Inną alternatywą jest użycie hasha i sprawdzenie go. W przypadku małych kolekcji zwykle znacznie lepiej jest posortować lub indeksować maskę bitową. Hashtable mają wadę gorszej lokalizacji, więc miej to na uwadze. Ponownie, to tylko wtedy, gdy nie dbam o duplikaty. Jeśli chcesz uwzględnić duplikaty, posortuj oba.
źródło
W wielu przypadkach jedyną właściwą odpowiedzią jest odpowiedź Igora Ostrowskiego, inne odpowiedzi opierają się na kodzie skrótu obiektów. Ale kiedy generujesz kod skrótu dla obiektu, robisz to tylko na podstawie jego IMMUTABLE pól - takich jak pole ID obiektu (w przypadku encji bazy danych) - dlaczego ważne jest, aby przesłonić GetHashCode, gdy metoda Equals jest nadpisywana?
Oznacza to, że jeśli porównasz dwie kolekcje, wynik może być prawdziwy dla metody porównania, nawet jeśli pola różnych elementów nie są równe. Aby dokładnie porównać kolekcje, musisz użyć metody Igora i zaimplementować IEqualirity.
Przeczytaj komentarze moje i pana Schnidera do jego postu, na który najczęściej głosowano.
James
źródło
Pozwalając na duplikaty w
IEnumerable<T>
(jeśli zestawy nie są pożądane \ możliwe) i "ignorując kolejność", powinieneś móc użyć pliku.GroupBy()
.Nie jestem ekspertem w pomiarach złożoności, ale moje podstawowe zrozumienie jest takie, że powinno to być O (n). Rozumiem, że O (n ^ 2) pochodzi z wykonania operacji O (n) wewnątrz innej operacji O (n), takiej jak
ListA.Where(a => ListB.Contains(a)).ToList()
. Każdy element na LiścieB jest oceniany pod kątem równości względem każdego elementu na LiścieA.Jak powiedziałem, moje rozumienie złożoności jest ograniczone, więc popraw mnie, jeśli się mylę.
źródło
To proste rozwiązanie wymusza
IEnumerable
zaimplementowanie typu ogólnegoIComparable
. Z powoduOrderBy
definicji.Jeśli nie chcesz robić takiego założenia, ale nadal chcesz skorzystać z tego rozwiązania, możesz skorzystać z następującego fragmentu kodu:
źródło
Porównując na potrzeby twierdzeń testów jednostkowych, sensowne może być wyrzucenie trochę wydajności przez okno i po prostu przekonwertowanie każdej listy na reprezentację łańcuchową (csv) przed wykonaniem porównania. W ten sposób domyślny komunikat Assertion testu będzie wyświetlał różnice w komunikacie o błędzie.
Stosowanie:
Pomocnicza metoda rozszerzenia:
źródło