Czy istnieje wbudowana metoda porównywania kolekcji?


178

Chciałbym porównać zawartość kilku kolekcji w mojej metodzie Equals. Mam słownik i IList. Czy istnieje wbudowana metoda, aby to zrobić?

Edytowane: Chcę porównać dwa słowniki i dwa IListy, więc myślę, że to, co oznacza równość, jest jasne - jeśli dwa słowniki zawierają te same klucze odwzorowane na te same wartości, to są one równe.


Niezależnie od zamówienia czy nie IList? Pytanie jest dwuznaczne.
nawfal

Enumerable.SequenceEquali ISet.SetEqualspodaj wersje tej funkcjonalności. Jeśli chcesz być niezależny od zamówień i pracować z kolekcjami, które mają duplikaty, musisz rzucić własne. Sprawdź implementację sugerowaną w tym poście
ChaseMedallion

Odpowiedzi:


185

Enumerable.SequenceEqual

Określa, czy dwie sekwencje są równe, porównując ich elementy, używając określonego IEqualityComparer (T).

Nie można bezpośrednio porównać listy i słownika, ale można porównać listę wartości ze słownika z listą


52
Problem polega na tym, że SequenceEqual oczekuje, że elementy będą w tej samej kolejności. Klasa Dictionary nie gwarantuje kolejności kluczy lub wartości podczas wyliczania, więc jeśli zamierzasz użyć SequenceEqual, musisz najpierw posortować .Keys i .Values!
Orion Edwards,

3
@Orion: ... chyba że chcesz wykryć różnice w zamówieniach, oczywiście :-)
schoetbi

30
@schoetbi: Dlaczego chcesz wykryć różnice w zamówieniach w kontenerze, który nie gwarantuje zamówienia ?
Matti Virkkunen

4
@schoetbi: Służy do usuwania określonego elementu z IEnumerable. Jednak słownik nie gwarantuje kolejności, .Keysa zatem .Valuesmoże zwracać klucze i wartości w dowolnej kolejności, a ich kolejność może się zmieniać w miarę modyfikowania słownika. Sugeruję przeczytanie, czym jest Słownik, a czym nie jest.
Matti Virkkunen,

5
MS „TestTools i NUnit udostępniają CollectionAssert.AreEquivalent
tymtam

44

Jak inni sugerowali i zauważyli, SequenceEqualjest wrażliwy na zamówienia. Aby rozwiązać ten problem, możesz posortować słownik według klucza (który jest unikalny, a zatem sortowanie jest zawsze stabilne), a następnie użyć SequenceEqual. Poniższe wyrażenie sprawdza, czy dwa słowniki są równe bez względu na ich porządek wewnętrzny:

dictionary1.OrderBy(kvp => kvp.Key).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

EDYCJA: Jak zauważył Jeppe Stig Nielsen, niektóre obiekty mają IComparer<T>niezgodne z nimi obiekty IEqualityComparer<T>, co daje nieprawidłowe wyniki. Używając kluczy z takim obiektem, musisz określić poprawność IComparer<T>tych kluczy. Na przykład w przypadku kluczy łańcuchowych (które wykazują ten problem) należy wykonać następujące czynności, aby uzyskać prawidłowe wyniki:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))

Co jeśli typ klucza nie będzie CompareTo? Twoje rozwiązanie wybuchnie wtedy. A jeśli typ klucza ma domyślny moduł porównujący, który jest niezgodny z jego domyślnym modułem porównującym? Tak jest w przypadku string. Na przykład te słowniki (z domyślnymi porównywalnikami równości) zakończą się niepowodzeniem (pod wszystkimi informacjami o kulturze, o których wiem):var dictionary1 = new Dictionary<string, int> { { "Strasse", 10 }, { "Straße", 20 }, }; var dictionary2 = new Dictionary<string, int> { { "Straße", 20 }, { "Strasse", 10 }, };
Jeppe Stig Nielsen,

@JeppeStigNielsen: Jeśli chodzi o niezgodność między ICompareri IEqualityComparer- nie byłem świadomy tego problemu, bardzo interesujące! Zaktualizowałem odpowiedź możliwym rozwiązaniem. Jeśli chodzi o brak CompareTo, myślę, że programista powinien upewnić się, że delegat przekazany do OrderBy()metody zwraca coś porównywalnego. Myślę, że jest to prawdą w przypadku każdego zastosowania OrderBy(), a nawet poza porównaniami słownikowymi.
Allon Guralnek

15

Oprócz wspomnianego SequenceEqual , który

jest prawdą, jeśli dwie listy są równej długości, a odpowiadające im elementy porównują się równo według porównania

(który może być domyślnym programem porównującym, tzn. przesłonięciem Equals())

warto wspomnieć, że w .Net4 jest SetEquals na ISetobiektach, które

ignoruje kolejność elementów i wszelkich zduplikowanych elementów.

Więc jeśli chcesz mieć listę obiektów, ale nie muszą one znajdować się w określonej kolejności, zastanów się, że ISet(jak a HashSet) może być właściwym wyborem.


7

Spójrz na metodę Enumerable.SequenceEqual

var dictionary = new Dictionary<int, string>() {{1, "a"}, {2, "b"}};
var intList = new List<int> {1, 2};
var stringList = new List<string> {"a", "b"};
var test1 = dictionary.Keys.SequenceEqual(intList);
var test2 = dictionary.Values.SequenceEqual(stringList);

13
Nie jest to wiarygodne, ponieważ SequenceEqual oczekuje, że wartości będą wychodzić ze słownika w niezawodnej kolejności - słownik nie daje takich gwarancji dotyczących kolejności i słownika. Klucze mogą równie dobrze wynosić jako [2, 1] zamiast [1, 2] i twój test się nie powiedzie
Orion Edwards

5

.NET Brakuje potężnych narzędzi do porównywania kolekcji. Opracowałem proste rozwiązanie, które można znaleźć pod linkiem poniżej:

http://robertbouillon.com/2010/04/29/comparing-collections-in-net/

Spowoduje to porównanie równości bez względu na kolejność:

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

Spowoduje to sprawdzenie, czy elementy zostały dodane / usunięte:

var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added;   //Sally
var inbothlists = diff.Equal;   //Bob

To zobaczy, które elementy w słowniku się zmieniły:

var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
  Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa

10
Oczywiście .NET ma potężne narzędzia do porównywania kolekcji (są to operacje oparte na zestawach). .Removedjest taki sam jak list1.Except(list2), .Addedjest list2.Except(list1), .Equaljest list1.Intersect(list2)i .Differentjest original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Możesz wykonać prawie każde porównanie z LINQ.
Allon Guralnek

3
Korekta: .Differentjest original.Join(changed, left => left.Key, right => right.Key, (left, right) => new { Key = left.Key, NewValue = right.Value, Different = left.Value == right.Value).Where(d => d.Different). Możesz nawet dodać, OldValue = left.Valuejeśli potrzebujesz starej wartości.
Allon Guralnek

3
@AllonGuralnek twoje sugestie są dobre, ale nie uwzględniają przypadku, w którym lista nie jest prawdziwym zestawem - gdzie lista zawiera ten sam obiekt wiele razy. Porównując {1, 2} i {1, 2, 2} nie zwróciłoby nic dodanego / usuniętego.
Niall Connaughton,


4

Nie wiedziałem o metodzie Enumerable.SequenceEqual (uczysz się czegoś codziennie ....), ale zamierzałem zasugerować użycie metody rozszerzenia; coś takiego:

    public static bool IsEqual(this List<int> InternalList, List<int> ExternalList)
    {
        if (InternalList.Count != ExternalList.Count)
        {
            return false;
        }
        else
        {
            for (int i = 0; i < InternalList.Count; i++)
            {
                if (InternalList[i] != ExternalList[i])
                    return false;
            }
        }

        return true;

    }

Co ciekawe, po poświęceniu 2 sekund na przeczytanie o SequenceEqual wygląda na to, że Microsoft zbudował dla ciebie funkcję, którą opisałem.


4

To nie jest bezpośrednia odpowiedź na twoje pytania, ale zarówno MS TestTools, jak i NUnit zapewniają

 CollectionAssert.AreEquivalent

który robi prawie wszystko, co chcesz.


Szukałem tego do mojego testu NUnit
Blem

1

Aby porównać kolekcje, możesz również użyć LINQ. Enumerable.Intersectzwraca wszystkie pary, które są równe. Możesz porównać dwa takie słowniki:

(dict1.Count == dict2.Count) && dict1.Intersect(dict2).Count() == dict1.Count

Pierwsze porównanie jest potrzebne, ponieważ dict2może zawierać wszystkie klucze od dict1i więcej.

Można również użyć myśleć odmianach wykorzystujących Enumerable.Excepti Enumerable.Unionktóre prowadzą do podobnych wyników. Ale można go użyć do ustalenia dokładnych różnic między zestawami.


1

Co powiesz na ten przykład:

 static void Main()
{
    // Create a dictionary and add several elements to it.
    var dict = new Dictionary<string, int>();
    dict.Add("cat", 2);
    dict.Add("dog", 3);
    dict.Add("x", 4);

    // Create another dictionary.
    var dict2 = new Dictionary<string, int>();
    dict2.Add("cat", 2);
    dict2.Add("dog", 3);
    dict2.Add("x", 4);

    // Test for equality.
    bool equal = false;
    if (dict.Count == dict2.Count) // Require equal count.
    {
        equal = true;
        foreach (var pair in dict)
        {
            int value;
            if (dict2.TryGetValue(pair.Key, out value))
            {
                // Require value be equal.
                if (value != pair.Value)
                {
                    equal = false;
                    break;
                }
            }
            else
            {
                // Require key be present.
                equal = false;
                break;
            }
        }
    }
    Console.WriteLine(equal);
}

Dzięki uprzejmości: https://www.dotnetperls.com/dictionary-equals


wartość! = para.Value dokonuje porównania referencji, zamiast tego użyj Equals
kofifus

1

Do uporządkowanych kolekcji (Lista, Tablica) użyj SequenceEqual

do użytku HashSet SetEquals

W przypadku słownika możesz:

namespace System.Collections.Generic {
  public static class ExtensionMethods {
    public static bool DictionaryEquals<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> d1, IReadOnlyDictionary<TKey, TValue> d2) {
      if (object.ReferenceEquals(d1, d2)) return true; 
      if (d2 is null || d1.Count != d2.Count) return false;
      foreach (var (d1key, d1value) in d1) {
        if (!d2.TryGetValue(d1key, out TValue d2value)) return false;
        if (!d1value.Equals(d2value)) return false;
      }
      return true;
    }
  }
}

(Bardziej zoptymalizowane rozwiązanie będzie korzystało z sortowania, ale będzie wymagało IComparable<TValue>)


0

Nie. Ramy kolekcji nie mają żadnej koncepcji równości. Jeśli się nad tym zastanowić, nie ma sposobu na porównanie kolekcji, które nie są subiektywne. Na przykład porównując swoją IListę ze Słownikiem, czy byłyby one równe, gdyby wszystkie klucze były w IList, wszystkie wartości były w IList, czy oba były w IList? Nie ma oczywistego sposobu na porównanie tych dwóch kolekcji bez wiedzy o tym, do czego mają być użyte, więc metoda równości ogólnego przeznaczenia nie ma sensu.



0
public bool CompareStringLists(List<string> list1, List<string> list2)
{
    if (list1.Count != list2.Count) return false;

    foreach(string item in list1)
    {
        if (!list2.Contains(item)) return false;
    }

    return true;
}

0

Nie było, nie ma i może nie być, przynajmniej w to uwierzę. Przyczyną takiego stanu rzeczy jest prawdopodobnie zachowanie zdefiniowane przez użytkownika.

Elementy w kolekcjach nie powinny znajdować się w określonej kolejności, chociaż mają naturalnie uporządkowanie, nie na tym powinny polegać algorytmy porównujące. Załóżmy, że masz dwie kolekcje:

{1, 2, 3, 4}
{4, 3, 2, 1}

Czy są równi czy nie? Musisz wiedzieć, ale nie wiem jaki jest twój punkt widzenia.

Kolekcje są domyślnie nieuporządkowane koncepcyjnie, dopóki algorytmy nie zapewnią reguł sortowania. Ta sama rzecz, na którą zwraca uwagę serwer SQL, podczas próby podziału na strony, wymaga podania reguł sortowania:

https://docs.microsoft.com/en-US/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017

Kolejne dwie kolekcje:

{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}

Znów są równe czy nie? Ty mi powiedz ..

Powtarzalność elementów kolekcji odgrywa swoją rolę w różnych scenariuszach, a niektóre kolekcje, takie jak Dictionary<TKey, TValue>, nawet nie zezwalają na powtarzające się elementy.

Uważam, że tego rodzaju równość jest definiowana przez aplikację, dlatego ramy nie zapewniły wszystkich możliwych implementacji.

Cóż, w ogólnych przypadkach Enumerable.SequenceEqualjest wystarczająco dobry, ale zwraca false w następującym przypadku:

var a = new Dictionary<String, int> { { "2", 2 }, { "1", 1 }, };
var b = new Dictionary<String, int> { { "1", 1 }, { "2", 2 }, };
Debug.Print("{0}", a.SequenceEqual(b)); // false

Przeczytałem niektóre odpowiedzi na takie pytania (możesz je znaleźć w Google ) i ogólnie, czego bym użył:

public static class CollectionExtensions {
    public static bool Represents<T>(this IEnumerable<T> first, IEnumerable<T> second) {
        if(object.ReferenceEquals(first, second)) {
            return true;
        }

        if(first is IOrderedEnumerable<T> && second is IOrderedEnumerable<T>) {
            return Enumerable.SequenceEqual(first, second);
        }

        if(first is ICollection<T> && second is ICollection<T>) {
            if(first.Count()!=second.Count()) {
                return false;
            }
        }

        first=first.OrderBy(x => x.GetHashCode());
        second=second.OrderBy(x => x.GetHashCode());
        return CollectionExtensions.Represents(first, second);
    }
}

Oznacza to, że jedna kolekcja reprezentuje drugą w swoich elementach, w tym wielokrotnie powtarzanych, bez uwzględnienia pierwotnego porządku. Niektóre uwagi dotyczące wdrożenia:

  • GetHashCode()służy tylko porządkowi, a nie równości; Myślę, że w tym przypadku wystarczy

  • Count() tak naprawdę nie wyliczy kolekcji i bezpośrednio wpisze się w implementację właściwości ICollection<T>.Count

  • Jeśli referencje są równe, to tylko Boris

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.