Gibt es eine integrierte Methode zum Vergleichen von Sammlungen?

178

Ich möchte den Inhalt einiger Sammlungen in meiner Equals-Methode vergleichen. Ich habe ein Wörterbuch und eine IList. Gibt es eine eingebaute Methode, um dies zu tun?

Bearbeitet: Ich möchte zwei Wörterbücher und zwei IListen vergleichen, daher denke ich, dass die Bedeutung von Gleichheit klar ist. Wenn die beiden Wörterbücher dieselben Schlüssel enthalten, die denselben Werten zugeordnet sind, sind sie gleich.

TimK
quelle
Unabhängig von der Bestellung oder nicht für IList? Die Frage ist nicht eindeutig.
Nawfal
Enumerable.SequenceEqualund ISet.SetEqualsstellen Versionen dieser Funktionalität bereit. Wenn Sie auftragsunabhängig sein und mit Sammlungen arbeiten möchten, die Duplikate enthalten, müssen Sie Ihre eigenen rollen. Schauen Sie sich die in diesem Beitrag
ChaseMedallion

Antworten:

185

Enumerable.SequenceEqual

Bestimmt, ob zwei Sequenzen gleich sind, indem ihre Elemente mithilfe eines angegebenen IEqualityComparer (T) verglichen werden.

Sie können die Liste und das Wörterbuch nicht direkt vergleichen, aber Sie können die Liste der Werte aus dem Wörterbuch mit der Liste vergleichen

Glenn Slaven
quelle
52
Das Problem ist, dass SequenceEqual erwartet, dass die Elemente in derselben Reihenfolge vorliegen. Die Dictionary-Klasse garantiert nicht die Reihenfolge der Schlüssel oder Werte bei der Aufzählung. Wenn Sie also SequenceEqual verwenden möchten, müssen Sie zuerst die .Keys und .Values ​​sortieren!
Orion Edwards
3
@Orion: ... es sei denn, Sie möchten Bestellunterschiede feststellen, natürlich :-)
schoetbi
30
@schoetbi: Warum sollten Sie Bestellunterschiede in einem Container feststellen, der keine Bestellung garantiert ?
Matti Virkkunen
4
@schoetbi: Es dient dazu, ein bestimmtes Element aus einem IEnumerable zu entfernen. Ein Wörterbuch garantiert jedoch keine Reihenfolge .Keysund .Valueskann daher die Schlüssel und Werte in beliebiger Reihenfolge zurückgeben. Diese Reihenfolge wird sich wahrscheinlich ändern, wenn auch das Wörterbuch geändert wird. Ich schlage vor, Sie lesen nach, was ein Wörterbuch ist und was nicht.
Matti Virkkunen
5
MS 'TestTools und NUnit bieten CollectionAssert.AreEquivalent
tymtam
44

Wie andere vorgeschlagen und festgestellt haben, SequenceEqualist es auftragsabhängig. Um dies zu lösen, können Sie das Wörterbuch nach Schlüssel sortieren (was eindeutig ist und die Sortierung daher immer stabil ist) und dann verwenden SequenceEqual. Der folgende Ausdruck prüft, ob zwei Wörterbücher unabhängig von ihrer internen Reihenfolge gleich sind:

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

EDIT: Wie von Jeppe Stig Nielsen hervorgehoben, haben einige Objekte eine IComparer<T>, die mit ihren nicht kompatibel ist IEqualityComparer<T>, was zu falschen Ergebnissen führt. Wenn Sie Schlüssel mit einem solchen Objekt verwenden, müssen Sie eine Korrektur IComparer<T>für diese Schlüssel angeben . Bei Zeichenfolgenschlüsseln (bei denen dieses Problem auftritt) müssen Sie beispielsweise Folgendes tun, um korrekte Ergebnisse zu erhalten:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
Allon Guralnek
quelle
Was ist, wenn der Schlüsseltyp dies nicht CompareTotut? Ihre Lösung wird dann explodieren. Was ist, wenn der Schlüsseltyp einen Standardvergleicher hat, der nicht mit seinem Standardgleichheitsvergleich kompatibel ist? Dies ist der Fall für string, wissen Sie. Als Beispiel werden diese Wörterbücher (mit impliziten Standard-Gleichheitsvergleichen) Ihren Test nicht bestehen (unter allen mir bekannten Kulturinformationen):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: Was die Inkompatibilität zwischen IComparerund betrifft IEqualityComparer- ich war mir dieses Problems nicht bewusst, sehr interessant! Ich habe die Antwort mit einer möglichen Lösung aktualisiert. Über den Mangel an CompareTo, ich denke , die Entwickler sollten Sie die Delegierten der bereitgestellten machen OrderBy()Methode gibt etwas Vergleichbares. Ich denke, dies gilt für jede Verwendung oder OrderBy()auch außerhalb von Wörterbuchvergleichen.
Allon Guralnek
15

Neben der erwähnten SequenceEqual , die

ist wahr, wenn zwei Listen gleich lang sind und ihre entsprechenden Elemente nach einem Vergleicher gleich sind

(Dies kann der Standardvergleich sein, dh eine Überschreibung Equals())

Es ist erwähnenswert, dass es in .Net4 SetEquals für ISetObjekte gibt, die

ignoriert die Reihenfolge der Elemente und aller doppelten Elemente.

Wenn Sie also eine Liste von Objekten haben möchten, diese jedoch nicht in einer bestimmten Reihenfolge vorliegen müssen, sollten Sie berücksichtigen, dass ein ISet(wie a HashSet) die richtige Wahl sein kann.

Schicksal
quelle
7

Schauen Sie sich die Enumerable.SequenceEqual- Methode an

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);
aku
quelle
13
Dies ist nicht zuverlässig, da SequenceEqual erwartet, dass die Werte in einer zuverlässigen Reihenfolge aus dem Wörterbuch kommen. Das Wörterbuch übernimmt keine solchen Garantien für die Reihenfolge und das Wörterbuch. Schlüssel könnten durchaus als [2, 1] anstelle von [1, 2] ausgegeben werden. und Ihr Test würde fehlschlagen
Orion Edwards
5

.NET Es fehlen leistungsstarke Tools zum Vergleichen von Sammlungen. Ich habe eine einfache Lösung entwickelt, die Sie unter dem folgenden Link finden:

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

Dadurch wird unabhängig von der Reihenfolge ein Gleichheitsvergleich durchgeführt:

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

Dadurch wird überprüft, ob Elemente hinzugefügt / entfernt wurden:

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

Dadurch wird angezeigt, welche Elemente im Wörterbuch geändert wurden:

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
user329244
quelle
10
Natürlich verfügt .NET über leistungsstarke Tools zum Vergleichen von Sammlungen (es handelt sich um satzbasierte Vorgänge). .Removedist das gleiche wie list1.Except(list2), .Addedist list2.Except(list1), .Equalist list1.Intersect(list2)und .Differentist original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Sie können fast jeden Vergleich mit LINQ durchführen.
Allon Guralnek
3
Korrektur: .Differentist 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). Und Sie können sogar hinzufügen, OldValue = left.Valuewenn Sie auch den alten Wert benötigen.
Allon Guralnek
3
@AllonGuralnek Ihre Vorschläge sind gut, aber sie behandeln nicht den Fall, dass die Liste keine echte Menge ist - wo die Liste dasselbe Objekt mehrmals enthält. Wenn Sie {1, 2} und {1, 2, 2} vergleichen, wird nichts hinzugefügt / entfernt.
Niall Connaughton
4

Ich wusste nichts über die Enumerable.SequenceEqual-Methode (Sie lernen jeden Tag etwas ...), aber ich wollte die Verwendung einer Erweiterungsmethode vorschlagen. etwas wie das:

    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;

    }

Interessanterweise scheint Microsoft nach 2 Sekunden zum Lesen von SequenceEqual die von mir für Sie beschriebene Funktion erstellt zu haben.

Giovanni Galbo
quelle
4

Dies beantwortet Ihre Fragen nicht direkt, sondern wird sowohl von den TestTools als auch von NUnit der MS bereitgestellt

 CollectionAssert.AreEquivalent

das macht so ziemlich was du willst.

Tymtam
quelle
Ich suchte dies für meinen NUnit-Test
Blem
1

Zum Vergleichen von Sammlungen können Sie auch LINQ verwenden. Enumerable.IntersectGibt alle Paare zurück, die gleich sind. Sie können zwei Wörterbücher wie folgt vergleichen:

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

Der erste Vergleich ist erforderlich, da er dict2alle Schlüssel von dict1und mehr enthalten kann.

Sie können auch denken Variationen verwenden Verwendung Enumerable.Exceptund Enumerable.Uniondiese führen zu ähnlichen Ergebnissen. Kann aber verwendet werden, um die genauen Unterschiede zwischen Sätzen zu bestimmen.

Chrono
quelle
1

Wie wäre es mit diesem Beispiel:

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

Mit freundlicher Genehmigung: https://www.dotnetperls.com/dictionary-equals

ispostback
quelle
value! = pair.Value führt einen Referenzvergleich durch, verwenden Sie stattdessen Equals
kofifus
1

Für bestellte Sammlungen (Liste, Array) verwenden SequenceEqual

für die Verwendung von HashSet SetEquals

Für Dictionary können Sie Folgendes tun:

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

(Eine optimierte Lösung verwendet Sortierung, dies erfordert jedoch IComparable<TValue>)

Kofifus
quelle
0

Nein. Der Sammlungsrahmen hat kein Konzept der Gleichheit. Wenn Sie darüber nachdenken, gibt es keine Möglichkeit, Sammlungen zu vergleichen, die nicht subjektiv sind. Wenn Sie beispielsweise Ihre IList mit Ihrem Wörterbuch vergleichen, wären sie gleich, wenn sich alle Schlüssel in der IList, alle Werte in der IList oder beide in der IList befinden würden? Es gibt keine offensichtliche Möglichkeit, diese beiden Sammlungen zu vergleichen, ohne zu wissen, wofür sie verwendet werden sollen. Daher macht eine Methode für allgemeine Zwecke gleich keinen Sinn.

Der böse Andy
quelle
0

Nein, da das Framework nicht weiß, wie der Inhalt Ihrer Listen verglichen werden soll.

Schau dir das an:

http://blogs.msdn.com/abhinaba/archive/2005/10/11/479537.aspx

Mark Ingram
quelle
3
Gibt es nicht bereits mehrere Optionen, um dem Framework mitzuteilen, wie die Elemente verglichen werden sollen? IComparer<T>, Zwingende object.Equals, IEquatable<T>, IComparable<T>...
Stefan Steinegger
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;
}
Entwickler
quelle
0

Es gab, gibt und gibt es nicht, zumindest würde ich das glauben. Der Grund dafür ist, dass die Gleichheit der Sammlung wahrscheinlich ein benutzerdefiniertes Verhalten ist.

Elemente in Sammlungen sollten nicht in einer bestimmten Reihenfolge sein, obwohl sie natürlich eine Reihenfolge haben. Darauf sollten sich die Vergleichsalgorithmen nicht stützen. Angenommen, Sie haben zwei Sammlungen von:

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

Sind sie gleich oder nicht? Sie müssen wissen, aber ich weiß nicht, was Ihre Sichtweise ist.

Sammlungen sind standardmäßig konzeptionell ungeordnet, bis die Algorithmen die Sortierregeln bereitstellen. Wenn Sie versuchen, eine Paginierung durchzuführen, werden Sie auf SQL Server aufmerksam gemacht. Dazu müssen Sie Sortierregeln angeben:

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

Noch zwei Sammlungen:

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

Sind sie wieder gleich oder nicht? Du sagst es mir ..

Die Wiederholbarkeit von Elementen einer Sammlung spielt in verschiedenen Szenarien eine Rolle, und einige Sammlungen Dictionary<TKey, TValue>erlauben nicht einmal wiederholte Elemente.

Ich glaube, diese Art der Gleichstellung ist anwendungsdefiniert und der Rahmen bot daher nicht alle möglichen Implementierungen.

Nun, im Allgemeinen sind Fälle Enumerable.SequenceEqualgut genug, aber im folgenden Fall wird false zurückgegeben:

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

Ich habe einige Antworten auf Fragen wie diese gelesen (Sie können sie googeln ) und was ich im Allgemeinen verwenden würde:

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

Das heißt, eine Sammlung repräsentiert die andere in ihren Elementen, einschließlich wiederholter Zeiten, ohne die ursprüngliche Bestellung zu berücksichtigen. Einige Hinweise zur Implementierung:

  • GetHashCode()ist nur für die Bestellung nicht für die Gleichheit; Ich denke es ist genug in diesem Fall

  • Count() wird die Sammlung nicht wirklich aufzählen und direkt in die Eigenschaftsimplementierung von fallen ICollection<T>.Count

  • Wenn die Referenzen gleich sind, ist es nur Boris

Ken Kin
quelle