Koleksiyonları karşılaştırmak için yerleşik bir yöntem var mı?


178

Eşit yöntemimde birkaç koleksiyonun içeriğini karşılaştırmak istiyorum. Bir sözlüğüm ve bir IListim var. Bunu yapmak için yerleşik bir yöntem var mı?

Düzenlendi: İki Sözlük ve iki IListi karşılaştırmak istiyorum, bu yüzden eşitliğin ne anlama geldiğinin açık olduğunu düşünüyorum - eğer iki sözlük aynı değerlere eşlenmiş aynı anahtarları içeriyorsa, o zaman eşittir.


Sipariş ne olursa olsun, değil IListmi? Soru belirsiz.
nawfal

Enumerable.SequenceEqualve ISet.SetEqualsbu işlevin sürümlerini sağlar. Siparişten bağımsız olmak ve kopyaları olan koleksiyonlarla çalışmak istiyorsanız, kendinizinkini yuvarlamanız gerekir. Bu yazıda
ChaseMedallion

Yanıtlar:


185

Enumerable.SequenceEqual

Belirtilen bir IEqualityComparer (T) kullanarak öğelerini karşılaştırarak iki dizinin eşit olup olmadığını belirler.

Listeyi ve sözlüğü doğrudan karşılaştıramazsınız, ancak Sözlük'teki değer listesini listeyle karşılaştırabilirsiniz


52
Sorun, SequenceEqual öğelerinin aynı sırada olmasını beklemesidir. Dictionary sınıfı numaralandırma sırasında anahtarların veya değerlerin sırasını garanti etmez, bu nedenle SequenceEqual kullanacaksanız, önce .Keys ve .Values ​​değerlerini sıralamanız gerekir!
Orion Edwards

3
@Orion: ... tabii ki sipariş farklılıklarını tespit etmek istemiyorsanız, :-)
schoetbi

30
@schoetbi: Siparişi garanti etmeyen bir kaptaki sipariş farklılıklarını neden tespit etmek istersiniz ?
Matti Virkkunen

4
@schoetbi: Belirli bir öğeyi IEnumerable'dan çıkarmak içindir. Ancak, bir sözlük yüzden değil garanti sırasını yapar .Keysve .Valuesonlar gibi hissediyorum herhangi bir sırada anahtarları ve değerleri döndürebilir, ve sözlük sıra modifiye edildiği gibi bu düzen değişikliği olması mümkündür. Bir Sözlüğün ne olduğunu ve ne olmadığını okumanızı öneririm.
Matti Virkkunen

5
MS 'TestTools ve NUnit CollectionAssert.AreEquivalent sağlar
timtam

44

Diğerlerinin önerdiği ve belirttiği gibi SequenceEqual, siparişe duyarlıdır. Bunu çözmek için sözlüğü anahtar ile sıralayabilirsiniz (bu benzersizdir ve bu nedenle sıralama her zaman sabittir) ve ardından kullanın SequenceEqual. Aşağıdaki ifade, iç sözlüklerinden bağımsız olarak iki sözlüğün eşit olup olmadığını kontrol eder:

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

DÜZENLEME: Jeppe Stig Nielsen'in işaret ettiği gibi, bazı nesnelerin kendileriyle IComparer<T>uyumsuz olan IEqualityComparer<T>ve yanlış sonuçlar veren bir nesnesi vardır . Böyle bir nesneyle anahtarlar kullanırken, IComparer<T>bu anahtarlar için bir doğru belirtmeniz gerekir . Örneğin, (bu sorunu gösteren) dize anahtarlarıyla, doğru sonuçları almak için aşağıdakileri yapmanız gerekir:

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

Anahtar türü olmazsa ne olur CompareTo? O zaman çözümünüz patlayacak. Anahtar türünde, varsayılan eşitlik karşılaştırıcısıyla uyumlu olmayan bir varsayılan karşılaştırıcı varsa ne olur? Bu böyle string, bilirsiniz. Örnek olarak bu sözlükler (örtük varsayılan eşitlik karşılaştırıcıları ile) testinizde başarısız olacaktır (farkında olduğum tüm kültür bilgileri altında):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: arasındaki uyumsuzluk gelince IComparerve IEqualityComparer- Bu sorunun farkında değildi, çok ilginç! Cevabı olası bir çözümle güncelledim. Eksikliği hakkında CompareTo, geliştiricinin OrderBy()yönteme sağlanan delege karşılaştırılabilir bir şey döndürdüğünden emin olmalıdır düşünüyorum . Bunun herhangi bir kullanım için OrderBy(), hatta sözlük karşılaştırmalarının dışında bile geçerli olduğunu düşünüyorum .
Allon Guralnek

15

Bahsedilen ek olarak SequenceEqual , burada

iki liste eşit uzunluktaysa ve karşılık gelen öğeleri bir karşılaştırıcıya göre eşit karşılaştırırsa doğrudur

(varsayılan karşılaştırıcı, yani geçersiz kılma olabilir Equals())

Net4'teISet nesneler üzerinde SetEquals olduğunu belirtmek gerekir.

öğelerin ve yinelenen öğelerin sırasını yok sayar.

Bu nedenle, nesnelerin bir listesine sahip olmak istiyorsanız, ancak belirli bir sırada olmaları gerekmiyorsa, ISet(a gibi HashSet) doğru seçim olabileceğini düşünün .


7

Enumerable.SequenceEqual yöntemine bir göz atın

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
Bu güvenilir değildir, çünkü SequenceEqual değerlerin sözlükten güvenilir bir sırayla çıkmasını bekler - Sözlük, düzen ve sözlük hakkında böyle bir garanti vermez.Anahtarlar [1, 2] yerine [2, 1] olarak ortaya çıkabilir ve testiniz başarısız olur
Orion Edwards

5

.NET Koleksiyon karşılaştırması için güçlü araçlar yoktur. Aşağıdaki bağlantıda bulabileceğiniz basit bir çözüm geliştirdim:

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

Bu, sıradan bağımsız olarak bir eşitlik karşılaştırması gerçekleştirecektir:

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

Bu, öğelerin eklenip eklenmediğini / kaldırıldığını kontrol eder:

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

Bu, sözlükteki hangi öğelerin değiştiğini görecektir:

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
Tabii ki .NET, koleksiyonları karşılaştırmak için güçlü araçlara sahiptir (bunlar set tabanlı işlemlerdir). , olduğu , olduğu ve olduğu ile .Removedaynıdır . LINQ ile hemen hemen her karşılaştırmayı yapabilirsiniz. list1.Except(list2).Addedlist2.Except(list1).Equallist1.Intersect(list2).Differentoriginal.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value)
Allon Güralnek

3
Düzeltme: .Differentolduğunu 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). Ve hatta OldValue = left.Valueeski değere ihtiyacınız varsa ekleyebilirsiniz .
Allon Güralnek

3
@AllonGuralnek önerileriniz iyidir, ancak Listenin gerçek bir küme olmadığı, listenin aynı nesneyi birden çok kez içerdiği durumla ilgilenmezler. {1, 2} ve {1, 2, 2} karşılaştırması, eklenen / kaldırılan hiçbir şeyi döndürmez.
Niall Connaughton


4

Enumerable.SequenceEqual yöntemi hakkında bilmiyordum (her gün bir şeyler öğrenirsiniz ....), ama bir uzantı yöntemi kullanmayı önerecektim; böyle bir şey:

    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;

    }

İlginçtir ki, SequenceEqual hakkında okumak için 2 saniye geçtikten sonra, Microsoft sizin için tanımladığım işlevi oluşturmuş gibi görünüyor.


4

Bu sorularınızı doğrudan yanıtlamıyor, ancak hem MS 'TestTools hem de NUnit

 CollectionAssert.AreEquivalent

ki bu da istediğinizi yapar.


Bunu NUnit testi için arıyordum
Blem

1

Koleksiyonları karşılaştırmak için LINQ da kullanabilirsiniz. Enumerable.Intersecteşit olan tüm çiftleri döndürür. Bunun gibi iki sözlük karşılaştırabilirsiniz:

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

İlk karşılaştırma gereklidir çünkü dict2tüm anahtarları dict1ve daha fazlasını içerebilir .

Kullanarak Enumerable.Exceptve Enumerable.Unionbenzer sonuçlara yol açan varyasyonları düşünmeyi de kullanabilirsiniz . Ancak kümeler arasındaki kesin farkları belirlemek için kullanılabilir.


1

Bu örnek nasıl olur:

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

Nezaket: https://www.dotnetperls.com/dictionary-equals


value! = pair.Value referans karşılaştırması yapıyor, bunun yerine Equals kullanın
kofifus

1

Sıralı koleksiyonlar için (Liste, Dizi) kullanın SequenceEqual

HashSet kullanımı için SetEquals

Sözlük için şunları yapabilirsiniz:

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

(Daha optimize edilmiş bir çözüm sıralamayı kullanır, ancak bu gerektirir IComparable<TValue>)


0

Hayır. Koleksiyon çerçevesinin herhangi bir eşitlik kavramı yoktur. Bunu düşünürseniz, öznel olmayan koleksiyonları karşılaştırmanın bir yolu yoktur. Örneğin, IList'inizi Sözlüğünüzle karşılaştırmak, tüm anahtarlar IList'teyse, tüm değerler IList'deyse veya her ikisi de IList'deyse eşit olur muydu? Bu iki koleksiyonu ne için kullanacakları konusunda bilgi sahibi olmadan karşılaştırmanın açık bir yolu yoktur, bu nedenle genel bir amaç eşittir yöntem anlamsızdır.



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

En azından ben inanırdım, değil ve olmayabilir de. Bunun nedeni, koleksiyon eşitliğinin muhtemelen kullanıcı tanımlı bir davranıştır.

Koleksiyonlardaki öğelerin doğal bir sıralaması olmasına rağmen belirli bir sıraya sahip olmaları gerekmez, karşılaştırma algoritmalarının güvenmesi gereken şey bu değildir. İki koleksiyonunuz olduğunu varsayalım:

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

Eşit mi, değil mi? Bilmelisin ama senin bakış açının ne olduğunu bilmiyorum.

Algoritmalar sıralama kurallarını sağlayana kadar koleksiyonlar varsayılan olarak kavramsal olarak sıralanmamıştır. SQL sunucusunun dikkatinize getireceği şey, sayfalandırma yapmaya çalıştığınızda, sıralama kuralları sağlamanızı gerektirir:

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

Yine iki koleksiyon daha var:

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

Yine eşitler mi değiller mi? Sen söyle ..

Bir koleksiyonun öğe tekrarlanabilirliği, farklı senaryolarda rolünü oynar ve Dictionary<TKey, TValue>tekrarlanan öğelere bile izin verme gibi bazı koleksiyonlar .

Bu tür eşitliklerin uygulama tarafından tanımlandığını ve bu nedenle çerçevenin tüm olası uygulamaları sağlamadığını düşünüyorum.

Genel durumlarda Enumerable.SequenceEqualyeterince iyidir, ancak aşağıdaki durumda yanlış döndürür:

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

Bu gibi sorulara bazı cevaplar okudum ( onlar için google olabilir ) ve genel olarak ne kullanacağımı:

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

Bu, bir koleksiyonun, orijinal siparişi hesaba katmadan tekrarlanan süreler dahil olmak üzere öğelerinde diğerini temsil ettiği anlamına gelir. Uygulamanın bazı notları:

  • GetHashCode()sadece eşitlik için değil düzen için; Bence bu durumda yeterli

  • Count() koleksiyonu gerçekten numaralandırmayacak ve doğrudan mülk uygulamasına düşmeyecek ICollection<T>.Count

  • Referanslar eşitse, sadece Boris

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.