Apakah ada metode bawaan untuk membandingkan koleksi?

178

Saya ingin membandingkan konten beberapa koleksi dalam metode Persamaan saya. Saya memiliki Kamus dan IList. Apakah ada metode bawaan untuk melakukan ini?

Diedit: Saya ingin membandingkan dua Kamus dan dua ILIST, jadi saya pikir apa artinya kesetaraan itu jelas - jika dua kamus berisi kunci yang sama yang dipetakan dengan nilai yang sama, maka keduanya sama.

TIMK
sumber
Terlepas dari pesanan atau tidak untuk IList? Pertanyaan tidak jelas.
nawfal
Enumerable.SequenceEqualdan ISet.SetEqualsmenyediakan versi fungsi ini. Jika Anda ingin menjadi agnostik ketertiban dan bekerja dengan koleksi yang memiliki duplikat, Anda harus menggulirnya sendiri. Lihatlah penerapan yang disarankan dalam pos ini
ChaseMedallion

Jawaban:

185

Enumerable.SequenceEqual

Menentukan apakah dua urutan sama dengan membandingkan elemen-elemen mereka dengan menggunakan IEqualityComparer (T) yang ditentukan.

Anda tidak dapat langsung membandingkan daftar & kamus, tetapi Anda bisa membandingkan daftar nilai dari Kamus dengan daftar

Glenn Slaven
sumber
52
Masalahnya adalah SequenceEqual mengharapkan elemen berada dalam urutan yang sama. Kelas Kamus tidak menjamin urutan kunci atau nilai saat enumerasi, jadi jika Anda akan menggunakan SequenceEqual, Anda harus mengurutkan .Keys dan .Values ​​terlebih dahulu!
Orion Edwards
3
@Orion: ... kecuali jika Anda ingin mendeteksi perbedaan pemesanan, tentu saja :-)
schoetbi
30
@schoetbi: Mengapa Anda ingin mendeteksi perbedaan pemesanan dalam wadah yang tidak menjamin pesanan ?
Matti Virkkunen
4
@ schoetbi: Ini untuk mengambil elemen tertentu dari IEnumerable. Namun, kamus tidak menjamin pesanan, jadi .Keysdan .Valuesdapat mengembalikan kunci dan nilai dalam urutan apa pun yang mereka suka, dan urutan itu kemungkinan akan berubah karena kamus juga dimodifikasi. Saya sarankan Anda membaca apa itu Kamus dan bukan.
Matti Virkkunen
5
MS 'TestTools dan NUnit menyediakan CollectionAssert.AreEquivalent
tymtam
44

Seperti yang disarankan dan dicatat oleh orang lain, SequenceEqualpeka terhadap pesanan. Untuk mengatasinya, Anda bisa mengurutkan kamus dengan kunci (yang unik, dan karenanya selalu stabil) dan kemudian gunakan SequenceEqual. Ungkapan berikut memeriksa apakah dua kamus sama tanpa memandang urutan internalnya:

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

EDIT: Seperti yang ditunjukkan oleh Jeppe Stig Nielsen, beberapa objek memiliki IComparer<T>yang tidak sesuai dengan mereka IEqualityComparer<T>, menghasilkan hasil yang salah. Saat menggunakan kunci dengan objek seperti itu, Anda harus menentukan yang benar IComparer<T>untuk kunci-kunci itu. Misalnya, dengan kunci string (yang menunjukkan masalah ini), Anda harus melakukan hal berikut untuk mendapatkan hasil yang benar:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
Allon Guralnek
sumber
Bagaimana jika jenis kuncinya tidak akan CompareTo? Solusi Anda akan meledak kemudian. Bagaimana jika jenis kunci memiliki pembanding default yang tidak kompatibel dengan pembanding kesetaraan standarnya? Ini masalahnya string, Anda tahu. Sebagai contoh, kamus-kamus ini (dengan pembanding kesetaraan standar implisit) akan gagal dalam ujian Anda (berdasarkan semua info budaya yang saya ketahui):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: Adapun ketidakcocokan antara IComparerdan IEqualityComparer- Saya tidak mengetahui masalah ini, sangat menarik! Saya memperbarui jawabannya dengan solusi yang memungkinkan. Tentang kurangnya CompareTo, saya pikir pengembang harus memastikan delegasi yang disediakan untuk OrderBy()metode mengembalikan sesuatu yang sebanding. Saya pikir ini berlaku untuk penggunaan apa pun atau OrderBy(), bahkan di luar perbandingan kamus.
Allon Guralnek
15

Selain SequenceEqual yang disebutkan , yang

benar jika dua daftar memiliki panjang yang sama dan unsur-unsurnya yang sesuai membandingkan sama menurut pembanding

(yang mungkin merupakan pembanding default, yaitu overriden Equals())

Perlu disebutkan bahwa dalam. Net4 ada SetEquals pada ISetobjek, yang

mengabaikan urutan elemen dan elemen duplikat.

Jadi jika Anda ingin memiliki daftar objek, tetapi mereka tidak perlu dalam urutan tertentu, pertimbangkan bahwa ISet(seperti a HashSet) mungkin merupakan pilihan yang tepat.

Desty
sumber
7

Lihatlah metode 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);
aku
sumber
13
Ini tidak dapat diandalkan karena SequenceEqual mengharapkan nilai untuk keluar dari kamus dalam urutan yang dapat diandalkan - Kamus tidak memberikan jaminan semacam itu tentang pesanan, dan kamus. dan tes Anda akan gagal
Orion Edwards
5

.NET Tidak memiliki alat yang ampuh untuk membandingkan koleksi. Saya telah mengembangkan solusi sederhana yang dapat Anda temukan di tautan di bawah:

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

Ini akan melakukan perbandingan kesetaraan terlepas dari pesanan:

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

Ini akan memeriksa untuk melihat apakah item ditambahkan / dihapus:

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

Ini akan melihat item apa dalam kamus berubah:

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
pengguna329244
sumber
10
Tentu saja .NET memiliki alat yang kuat untuk membandingkan koleksi (mereka adalah operasi berbasis set). .Removedsama dengan list1.Except(list2), .Addedadalah list2.Except(list1), .Equaladalah list1.Intersect(list2)dan .Differentsekarang original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Anda dapat melakukan hampir semua perbandingan dengan LINQ.
Allon Guralnek
3
Koreksi: .Differentadalah 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). Dan Anda bahkan dapat menambahkan OldValue = left.Valuejika Anda membutuhkan nilai lama juga.
Allon Guralnek
3
@ AllonGuralnek saran Anda bagus, tetapi saran tersebut tidak menangani kasus di mana Daftar tidak benar - di mana daftar berisi objek yang sama beberapa kali. Membandingkan {1, 2} dan {1, 2, 2} tidak akan menghasilkan apa-apa yang ditambahkan / dihapus.
Niall Connaughton
4

Saya tidak tahu tentang metode Enumerable.SequenceEqual (Anda belajar sesuatu setiap hari ....), tapi saya akan menyarankan menggunakan metode ekstensi; sesuatu seperti ini:

    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;

    }

Cukup menarik, setelah mengambil 2 detik untuk membaca tentang SequenceEqual, sepertinya Microsoft telah membangun fungsi yang saya jelaskan untuk Anda.

Giovanni Galbo
sumber
4

Ini tidak langsung menjawab pertanyaan Anda, tetapi MS TestTools dan NUnit menyediakan

 CollectionAssert.AreEquivalent

yang melakukan apa yang Anda inginkan.

tymtam
sumber
Sedang mencari ini untuk tes NUnit saya
Blem
1

Untuk membandingkan koleksi, Anda juga dapat menggunakan LINQ. Enumerable.Intersectmengembalikan semua pasangan yang sama. Anda dapat membuat dua kamus seperti ini:

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

Diperlukan perbandingan pertama karena dict2dapat memuat semua kunci dari dict1dan banyak lagi.

Anda juga dapat menggunakan memikirkan variasi menggunakan Enumerable.Exceptdan Enumerable.Unionyang mengarah ke hasil yang serupa. Tetapi dapat digunakan untuk menentukan perbedaan yang tepat antara set.

Chrono
sumber
1

Bagaimana dengan contoh ini:

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

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

ispostback
sumber
value! = pair.Value melakukan perbandingan referensi, gunakan Equals sebagai gantinya
kofifus
1

Untuk koleksi yang dipesan (Daftar, Array) gunakan SequenceEqual

untuk penggunaan HashSet SetEquals

untuk Kamus dapat Anda lakukan:

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

(Solusi yang lebih optimal akan menggunakan penyortiran tetapi itu akan membutuhkan IComparable<TValue>)

kofifus
sumber
0

Tidak. Kerangka koleksi tidak memiliki konsep kesetaraan. Jika Anda memikirkannya, tidak ada cara membandingkan koleksi yang tidak subjektif. Misalnya membandingkan IList Anda dengan Kamus Anda, apakah mereka akan sama jika semua kunci ada di IList, semua nilai ada di IList atau jika keduanya ada di IList? Tidak ada cara yang jelas untuk membandingkan kedua koleksi ini tanpa pengetahuan tentang apa yang akan mereka gunakan sehingga tujuan umum sama dengan metode tidak masuk akal.

Andy yang jahat
sumber
0

Tidak, karena kerangka kerja tidak tahu bagaimana membandingkan konten daftar Anda.

Lihatlah ini:

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

Tandai Ingram
sumber
3
Bukankah sudah ada beberapa opsi untuk memberi tahu kerangka kerja bagaimana membandingkan elemen? IComparer<T>, Override 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;
}
mbadeveloper
sumber
0

Tidak ada, tidak dan mungkin tidak, setidaknya saya akan percaya begitu. Alasan di baliknya adalah kesetaraan koleksi mungkin merupakan perilaku yang ditentukan pengguna.

Elemen dalam koleksi tidak seharusnya dalam urutan tertentu meskipun mereka memiliki pemesanan secara alami, itu bukan apa yang harus mengandalkan algoritma pembanding. Katakanlah Anda memiliki dua koleksi:

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

Apakah mereka setara atau tidak? Anda harus tahu tetapi saya tidak tahu apa sudut pandang Anda.

Koleksi secara konsep tidak teratur secara default, hingga algoritme menyediakan aturan penyortiran. Hal yang sama dengan SQL server akan menarik perhatian Anda adalah ketika Anda mencoba melakukan pagination, itu mengharuskan Anda untuk memberikan aturan penyortiran:

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

Dua koleksi lagi:

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

Sekali lagi, apakah mereka setara atau tidak? Katakan pada saya ..

Pengulangan elemen koleksi memainkan perannya dalam skenario yang berbeda dan beberapa koleksi seperti Dictionary<TKey, TValue>bahkan tidak memungkinkan elemen berulang.

Saya percaya jenis kesetaraan ini adalah aplikasi yang ditentukan dan oleh karena itu kerangka kerjanya tidak menyediakan semua kemungkinan implementasi.

Nah, dalam kasus umum Enumerable.SequenceEqualcukup baik tetapi mengembalikan false dalam kasus berikut:

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

Saya membaca beberapa jawaban untuk pertanyaan seperti ini (Anda dapat menggunakan google untuk itu) dan apa yang akan saya gunakan, secara umum:

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

Itu berarti satu koleksi mewakili yang lain dalam elemen mereka termasuk waktu yang berulang tanpa memperhitungkan urutan aslinya. Beberapa catatan implementasi:

  • GetHashCode()hanya untuk pemesanan bukan untuk kesetaraan; Saya pikir sudah cukup dalam hal ini

  • Count() tidak akan benar - benar menyebutkan koleksi dan langsung jatuh ke dalam implementasi properti ICollection<T>.Count

  • Jika referensi sama, itu hanya Boris

Ken Kin
sumber