Membandingkan dua koleksi untuk kesetaraan terlepas dari urutan item di dalamnya

162

Saya ingin membandingkan dua koleksi (dalam C #), tapi saya tidak yakin cara terbaik untuk mengimplementasikan ini secara efisien.

Saya telah membaca utas lainnya tentang Enumerable.SequenceEqual , tapi bukan itu yang saya cari.

Dalam kasus saya, dua koleksi akan sama jika keduanya berisi item yang sama (tidak peduli urutannya).

Contoh:

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1 == collection2; // true

Apa yang biasanya saya lakukan adalah untuk mengulang setiap item dari satu koleksi dan melihat apakah ada di koleksi lain, kemudian loop melalui setiap item dari koleksi lain dan melihat apakah ada di koleksi pertama. (Saya mulai dengan membandingkan panjangnya).

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

Namun, ini tidak sepenuhnya benar, dan itu mungkin bukan cara yang paling efisien untuk membandingkan dua koleksi untuk kesetaraan.

Contoh yang bisa saya pikirkan adalah salah:

collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}

Yang akan sama dengan implementasi saya. Haruskah saya menghitung berapa kali setiap item ditemukan dan memastikan bahwa jumlahnya sama di kedua koleksi?


Contoh-contohnya ada dalam semacam C # (sebut saja pseudo-C #), tetapi berikan jawaban Anda dalam bahasa apa pun yang Anda inginkan, itu tidak masalah.

Catatan: Saya menggunakan bilangan bulat dalam contoh untuk kesederhanaan, tetapi saya ingin dapat menggunakan objek tipe referensi juga (mereka tidak berperilaku benar sebagai kunci karena hanya referensi objek dibandingkan, bukan konten).

mbillard
sumber
1
Bagaimana dengan algoritma? Semua jawaban terkait dengan membandingkan sesuatu, daftar generik membandingkan LINQ dll. Benarkah kami berjanji kepada seseorang bahwa kami tidak akan pernah menggunakan algoritma sebagai programmer kuno?
Nuri YILMAZ
Anda tidak memeriksa Kesetaraan, Anda sedang memeriksa Kesetaraan. Ini nitpicky tetapi perbedaan penting. Dan dulu sekali. Ini adalah Q + A yang bagus.
CAD berbicara
Anda mungkin tertarik pada postingan ini , yang membahas versi tuned dari metode berbasis kamus yang dijelaskan di bawah ini. Satu masalah dengan pendekatan kamus paling sederhana adalah bahwa mereka tidak menangani nulls dengan benar karena kelas .NET's Dictionary tidak mengizinkan kunci null
ChaseMedallion

Jawaban:

112

Ternyata Microsoft sudah membahas hal ini dalam kerangka pengujiannya: CollectionAssert.AreEquivalent

Catatan

Dua koleksi setara jika mereka memiliki elemen yang sama dalam jumlah yang sama, tetapi dalam urutan apa pun. Elemen sama jika nilainya sama, bukan jika merujuk ke objek yang sama.

Menggunakan reflektor, saya memodifikasi kode di belakang AreEquivalent () untuk membuat pembanding kesetaraan yang sesuai. Ini lebih lengkap daripada jawaban yang ada, karena memperhitungkan nol, mengimplementasikan IEqualityComparer dan memiliki beberapa efisiensi dan pemeriksaan tepi kasus. plus, ini Microsoft :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private readonly IEqualityComparer<T> m_comparer;
    public MultiSetComparer(IEqualityComparer<T> comparer = null)
    {
        m_comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

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

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstNullCount;
        int secondNullCount;

        var firstElementCounts = GetElementCounts(first, out firstNullCount);
        var secondElementCounts = GetElementCounts(second, out secondNullCount);

        if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            var firstElementCount = kvp.Value;
            int secondElementCount;
            secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

            if (firstElementCount != secondElementCount)
                return true;
        }

        return false;
    }

    private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>(m_comparer);
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + (val?.GetHashCode() ?? 42);

        return hash;
    }
}

Penggunaan sampel:

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

Atau jika Anda hanya ingin membandingkan dua koleksi secara langsung:

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

Akhirnya, Anda dapat menggunakan pembanding kesetaraan pilihan Anda:

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true
Ohad Schneider
sumber
7
Saya tidak 100% yakin tetapi saya pikir jawaban Anda melanggar ketentuan penggunaan Microsoft terhadap rekayasa balik.
Ian Dallas
1
Halo Ohad, Silakan baca debat panjang berikut dalam topik ini, stackoverflow.com/questions/371328/... Jika Anda mengubah kode hash objek, sementara dalam hashset, itu akan mengganggu hashset dengan tindakan yang tepat dan mungkin menyebabkan pengecualian. Aturannya adalah sebagai berikut: Jika dua objek sama - mereka harus memiliki kode hash yang sama. Jika dua objek memiliki kode hash yang sama - bukan suatu keharusan bagi mereka untuk menjadi sama. Hashcode harus tetap sama untuk seumur hidup seluruh objek! Itulah mengapa Anda menerapkan ICompareable dan IEqualrity.
James Roeiter
2
@ JamesRoeiter Mungkin komentar saya menyesatkan. Ketika kamus menemukan kode hash yang sudah berisi, itu memeriksa kesetaraan aktual dengan EqualityComparer(baik yang Anda berikan atau EqualityComparer.Default, Anda dapat memeriksa Reflektor atau sumber referensi untuk memverifikasi ini). Benar, jika objek berubah (dan khususnya kode hash mereka berubah) saat metode ini berjalan maka hasilnya tidak terduga, tetapi itu hanya berarti metode ini tidak aman dalam konteks ini.
Ohad Schneider
1
@ JamesRoeiter Misalkan x dan y adalah dua objek yang ingin kita bandingkan. Jika mereka memiliki kode hash yang berbeda, kita tahu mereka berbeda (karena item yang sama memiliki kode hash yang sama), dan implementasi di atas benar. Jika mereka memiliki kode hash yang sama, implementasi kamus akan memeriksa kesetaraan aktual menggunakan yang ditentukan EqualityComparer(atau EqualityComparer.Defaultjika tidak ada yang ditentukan) dan sekali lagi implementasinya benar.
Ohad Schneider
1
@CADbloke metode harus dinamai Equalskarena IEqualityComparer<T>antarmuka. Yang harus Anda lihat adalah nama pembanding itu sendiri . Dalam hal ini MultiSetCompareryang masuk akal.
Ohad Schneider
98

Solusi sederhana dan cukup efisien adalah menyortir kedua koleksi dan kemudian membandingkannya untuk kesetaraan:

bool equal = collection1.OrderBy(i => i).SequenceEqual(
                 collection2.OrderBy(i => i));

Algoritma ini adalah O (N * logN), sedangkan solusi Anda di atas adalah O (N ^ 2).

Jika koleksi memiliki sifat-sifat tertentu, Anda mungkin dapat menerapkan solusi yang lebih cepat. Misalnya, jika kedua koleksi Anda adalah kumpulan hash, mereka tidak dapat berisi duplikat. Juga, memeriksa apakah hash set mengandung beberapa elemen sangat cepat. Dalam hal ini algoritma yang mirip dengan Anda kemungkinan akan menjadi yang tercepat.

Sani Singh Huttunen
sumber
1
Anda hanya perlu menambahkan menggunakan System.Linq; pertama yang membuatnya bekerja
Junior Mayhé
jika kode ini berada dalam satu loop dan collection1 akan diperbarui dan collection2 tetap tidak tersentuh, perhatikan bahkan ketika kedua koleksi memiliki objek yang sama, debugger akan menunjukkan false untuk variabel "sama" ini.
Junior Mayhé
5
@Chaulky - Saya percaya OrderBy dibutuhkan. Lihat: dotnetfiddle.net/jA8iwE
Brett
Mana jawaban lain yang disebut "di atas"? Mungkin stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs
32

Buat Kamus "dict" dan kemudian untuk setiap anggota dalam koleksi pertama, lakukan dict [anggota] ++;

Kemudian, lingkarkan koleksi kedua dengan cara yang sama, tetapi untuk setiap anggota lakukan dikt [anggota] -.

Pada akhirnya, lingkar semua anggota dalam kamus:

    private bool SetEqual (List<int> left, List<int> right) {

        if (left.Count != right.Count)
            return false;

        Dictionary<int, int> dict = new Dictionary<int, int>();

        foreach (int member in left) {
            if (dict.ContainsKey(member) == false)
                dict[member] = 1;
            else
                dict[member]++;
        }

        foreach (int member in right) {
            if (dict.ContainsKey(member) == false)
                return false;
            else
                dict[member]--;
        }

        foreach (KeyValuePair<int, int> kvp in dict) {
            if (kvp.Value != 0)
                return false;
        }

        return true;

    }

Sunting: Sejauh yang saya tahu ini berada pada urutan yang sama dengan algoritma yang paling efisien. Algoritma ini adalah O (N), dengan asumsi bahwa Kamus menggunakan pencarian O (1).

Daniel Jennings
sumber
Ini hampir seperti yang saya inginkan. Namun, saya ingin dapat melakukan ini bahkan jika saya tidak menggunakan bilangan bulat. Saya ingin menggunakan objek referensi, tetapi mereka tidak berperilaku sebagaimana kunci dalam kamus.
mbillard
Mono, pertanyaan Anda bisa diperdebatkan jika Barang Anda tidak sebanding. Jika mereka tidak dapat digunakan sebagai kunci dalam Kamus, tidak ada solusi yang tersedia.
skolima
1
Saya pikir Mono berarti kunci tidak dapat disortir. Tetapi solusi Daniel jelas dimaksudkan untuk diimplementasikan dengan hashtable, bukan tree, dan akan bekerja selama ada tes kesetaraan dan fungsi hash.
erickson
Terpilih tentu saja untuk bantuan, tetapi tidak diterima karena tidak ada poin penting (yang saya bahas dalam jawaban saya).
mbillard
1
FWIW, Anda dapat menyederhanakan foreach loop terakhir Anda dan mengembalikan pernyataan dengan ini:return dict.All(kvp => kvp.Value == 0);
Tyson Williams
18

Ini adalah implementasi generik saya (sangat dipengaruhi oleh D.Jennings) dari metode perbandingan (dalam C #):

/// <summary>
/// Represents a service used to compare two collections for equality.
/// </summary>
/// <typeparam name="T">The type of the items in the collections.</typeparam>
public class CollectionComparer<T>
{
    /// <summary>
    /// Compares the content of two collections for equality.
    /// </summary>
    /// <param name="foo">The first collection.</param>
    /// <param name="bar">The second collection.</param>
    /// <returns>True if both collections have the same content, false otherwise.</returns>
    public bool Execute(ICollection<T> foo, ICollection<T> bar)
    {
        // Declare a dictionary to count the occurence of the items in the collection
        Dictionary<T, int> itemCounts = new Dictionary<T,int>();

        // Increase the count for each occurence of the item in the first collection
        foreach (T item in foo)
        {
            if (itemCounts.ContainsKey(item))
            {
                itemCounts[item]++;
            }
            else
            {
                itemCounts[item] = 1;
            }
        }

        // Wrap the keys in a searchable list
        List<T> keys = new List<T>(itemCounts.Keys);

        // Decrease the count for each occurence of the item in the second collection
        foreach (T item in bar)
        {
            // Try to find a key for the item
            // The keys of a dictionary are compared by reference, so we have to
            // find the original key that is equivalent to the "item"
            // You may want to override ".Equals" to define what it means for
            // two "T" objects to be equal
            T key = keys.Find(
                delegate(T listKey)
                {
                    return listKey.Equals(item);
                });

            // Check if a key was found
            if(key != null)
            {
                itemCounts[key]--;
            }
            else
            {
                // There was no occurence of this item in the first collection, thus the collections are not equal
                return false;
            }
        }

        // The count of each item should be 0 if the contents of the collections are equal
        foreach (int value in itemCounts.Values)
        {
            if (value != 0)
            {
                return false;
            }
        }

        // The collections are equal
        return true;
    }
}
mbillard
sumber
12
Pekerjaan yang bagus, tetapi Catatan: 1. Berbeda dengan solusi Daniel Jennings, Ini bukan O (N) melainkan O (N ^ 2), karena fungsi find di dalam foreach loop pada koleksi bar; 2. Anda dapat menggeneralisasi metode untuk menerima IEnumerable <T> alih-alih ICollection <T> tanpa modifikasi lebih lanjut pada kode
Ohad Schneider
The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"- ini tidak benar. Algoritma ini didasarkan pada asumsi yang salah dan sementara bekerja, itu sangat tidak efisien.
Antonín Lejsek
10

Anda bisa menggunakan Hashset . Lihatlah metode SetEquals .

Joel Gauvreau
sumber
2
tentu saja, menggunakan HashSet mengasumsikan tidak ada duplikat tetapi jika demikian HashSet adalah cara terbaik untuk pergi
Mark Cidade
7

Jika Anda menggunakan Shouldly , Anda bisa menggunakan ShouldAllBe with Contains.

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1.ShouldAllBe(item=>collection2.Contains(item)); // true

Dan akhirnya, Anda dapat menulis ekstensi.

public static class ShouldlyIEnumerableExtensions
{
    public static void ShouldEquivalentTo<T>(this IEnumerable<T> list, IEnumerable<T> equivalent)
    {
        list.ShouldAllBe(l => equivalent.Contains(l));
    }
}

MEMPERBARUI

Parameter opsional ada pada metode ShouldBe .

collection1.ShouldBe(collection2, ignoreOrder: true); // true
Dermaga-Lionel Sgard
sumber
1
Saya baru saja menemukan pada versi terbaru bahwa ada parameter bool ignoreOrderpada metode ShouldBe .
Dermaga-Lionel Sgard
5

EDIT: Saya menyadari segera setelah saya berpose bahwa ini benar-benar hanya berfungsi untuk set - itu tidak akan berurusan dengan koleksi yang memiliki item duplikat. Misalnya {1, 1, 2} dan {2, 2, 1} akan dianggap sama dari perspektif algoritma ini. Namun, jika koleksi Anda ditetapkan (atau kesetaraannya dapat diukur dengan cara itu), saya harap Anda menemukan di bawah ini berguna.

Solusi yang saya gunakan adalah:

return c1.Count == c2.Count && c1.Intersect(c2).Count() == c1.Count;

Linq melakukan hal kamus di bawah selimut, jadi ini juga O (N). (Catatan, ini O (1) jika koleksi tidak berukuran sama).

Saya melakukan pemeriksaan kewarasan menggunakan metode "SetEqual" yang disarankan oleh Daniel, metode OrderBy / SequenceEquals yang disarankan oleh Igor, dan saran saya. Hasilnya di bawah ini, menunjukkan O (N * LogN) untuk Igor dan O (N) untuk saya dan Daniel.

Saya pikir kesederhanaan dari kode interseksi Linq menjadikannya solusi yang lebih disukai.

__Test Latency(ms)__
N, SetEquals, OrderBy, Intersect    
1024, 0, 0, 0    
2048, 0, 0, 0    
4096, 31.2468, 0, 0    
8192, 62.4936, 0, 0    
16384, 156.234, 15.6234, 0    
32768, 312.468, 15.6234, 46.8702    
65536, 640.5594, 46.8702, 31.2468    
131072, 1312.3656, 93.7404, 203.1042    
262144, 3765.2394, 187.4808, 187.4808    
524288, 5718.1644, 374.9616, 406.2084    
1048576, 11420.7054, 734.2998, 718.6764    
2097152, 35090.1564, 1515.4698, 1484.223

sumber
Satu-satunya masalah dengan kode ini adalah bahwa ia hanya berfungsi ketika membandingkan tipe nilai atau membandingkan pointer ke tipe referensi. Saya dapat memiliki dua contoh berbeda dari objek yang sama dalam koleksi, jadi saya harus dapat menentukan cara membandingkan masing-masing. Bisakah Anda meneruskan delegasi perbandingan ke metode intersect?
mbillard
Tentu, Anda dapat melewati delegasi pembanding. Tetapi, perhatikan batasan di atas tentang set yang saya tambahkan, yang membatasi penerapannya.
Metode Intersect mengembalikan koleksi yang berbeda. Diberi a = {1,1,2} dan b = {2,2,1}, a.Intersect (b) .Count ()! = A.Count, yang menyebabkan ekspresi Anda mengembalikan false dengan benar. {1,2} .Count! = {1,1,2} .Count Lihat tautan [/ tautan] (Perhatikan bahwa kedua belah pihak dibuat berbeda sebelum perbandingan.)
Griffin
5

Jika tidak ada pengulangan dan tanpa urutan, EqualityComparer berikut dapat digunakan untuk memungkinkan koleksi sebagai kunci kamus:

public class SetComparer<T> : IEqualityComparer<IEnumerable<T>> 
where T:IComparable<T>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == second)
            return true;
        if ((first == null) || (second == null))
            return false;
        return first.ToHashSet().SetEquals(second);
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

Berikut ini adalah implementasi ToHashSet () yang saya gunakan. The algoritma kode hash berasal dari Jawa Efektif (dengan cara Jon Skeet).

Ohad Schneider
sumber
Apa gunanya Serializable untuk kelas Comparer? : o Anda juga dapat mengubah input ISet<T>untuk mengekspresikannya dimaksudkan untuk set (yaitu tidak ada duplikat).
nawfal
@nawfal terima kasih, tidak tahu apa yang saya pikirkan ketika saya menandainya Serializable ... Adapun ISet, ide di sini adalah untuk memperlakukan IEnumerablesebagai satu set (karena Anda harus IEnumerablememulai dengan), meskipun mempertimbangkan 0 upvotes di lebih 5 tahun yang mungkin bukan ide yang paling tajam: P
Ohad Schneider
4
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) {
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);
}

Solusi membutuhkan .NET 3.5 dan System.Collections.Genericnamespace. Menurut Microsoft , SymmetricExceptWithadalah operasi O (n + m) , dengan n mewakili jumlah elemen di set pertama dan m mewakili jumlah elemen di set kedua. Anda selalu bisa menambahkan pembanding kesetaraan ke fungsi ini jika perlu.

palswim
sumber
3

Mengapa tidak digunakan. Kecuali ()

// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =   names1.Except(names2);
// Execute the query.
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
     Console.WriteLine(s);

http://msdn.microsoft.com/en-us/library/bb397894.aspx

Korayem
sumber
2
Excepttidak akan berfungsi untuk menghitung item duplikat. Ini akan mengembalikan true untuk set {1,2,2} dan {1,1,2}.
Cristian Diaconescu
@CristiDiaconescu Anda dapat melakukan ".Distinct ()" terlebih dahulu untuk menghapus duplikat apa pun
Korayem
OP meminta [1, 1, 2] != [1, 2, 2]. Menggunakannya Distinctakan membuat mereka terlihat sama.
Cristian Diaconescu
2

Posting rangkap jenis, tetapi periksa solusi saya untuk membandingkan koleksi . Sederhana saja:

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

Posting asli di sini .

pengguna329244
sumber
1

erickson hampir benar: karena Anda ingin mencocokkan jumlah duplikat, Anda menginginkan sebuah Tas . Di Jawa, ini terlihat seperti:

(new HashBag(collection1)).equals(new HashBag(collection2))

Saya yakin C # memiliki implementasi Set bawaan. Saya akan menggunakannya dulu; jika kinerja merupakan masalah, Anda selalu dapat menggunakan implementasi Set yang berbeda, tetapi menggunakan antarmuka Set yang sama.

James A. Rosen
sumber
1

Inilah varian metode ekstensi dari jawaban ohadsc, seandainya bermanfaat bagi seseorang

static public class EnumerableExtensions 
{
    static public bool IsEquivalentTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if ((first == null) != (second == null))
            return false;

        if (!object.ReferenceEquals(first, second) && (first != null))
        {
            if (first.Count() != second.Count())
                return false;

            if ((first.Count() != 0) && HaveMismatchedElement<T>(first, second))
                return false;
        }

        return true;
    }

    private static bool HaveMismatchedElement<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts<T>(first, out firstCount);
        var secondElementCounts = GetElementCounts<T>(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts<T>(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    static private int GetHashCode<T>(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}
Eric J.
sumber
Seberapa baik kinerja ini, ada ide?
nawfal
Saya hanya menggunakan ini untuk koleksi kecil, jadi belum memikirkan kompleksitas Big-O atau melakukan benchmarking. HaveMismatchedElements sendiri adalah O (M * N) sehingga mungkin tidak berkinerja baik untuk koleksi besar.
Eric J.
Jika IEnumerable<T>ada pertanyaan, maka menelepon Count()bukanlah ide yang baik. Pendekatan jawaban asli Ohad untuk memeriksa apakah mereka ICollection<T>adalah ide yang lebih baik.
nawfal
1

Berikut adalah solusi yang merupakan perbaikan dari yang ini .

public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        IEqualityComparer<T> comparer = null)
    {
        var firstMap = first
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        var secondMap = second
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        if (firstMap.Keys.Count != secondMap.Keys.Count)
            return false;

        if (firstMap.Keys.Any(k1 => !secondMap.ContainsKey(k1)))
            return false;

        return firstMap.Keys.All(x => firstMap[x] == secondMap[x]);
    }
N73k
sumber
0

Ada banyak solusi untuk masalah ini. Jika Anda tidak peduli dengan duplikat, Anda tidak perlu mengurutkan keduanya. Pertama pastikan bahwa mereka memiliki jumlah item yang sama. Setelah itu mengurutkan salah satu koleksi. Kemudian, binsearch setiap item dari koleksi kedua di koleksi yang diurutkan. Jika Anda tidak menemukan item yang diberikan berhenti dan kembali salah. Kompleksitasnya: - mengurutkan koleksi pertama: N Log (N) - mencari setiap item dari yang kedua menjadi yang pertama: NLOG (N) sehingga Anda mendapatkan 2 * N * LOG (N) dengan asumsi mereka cocok dan Anda mencari semuanya. Ini mirip dengan kompleksitas penyortiran keduanya. Ini juga memberi Anda manfaat untuk berhenti lebih awal jika ada perbedaan. Namun, perlu diingat bahwa jika keduanya diurutkan sebelum Anda masuk ke perbandingan ini dan Anda mencoba mengurutkan dengan menggunakan sesuatu seperti qsort, pengurutan akan lebih mahal. Ada optimisasi untuk ini. Alternatif lain, yang sangat bagus untuk koleksi kecil di mana Anda tahu kisaran elemen adalah dengan menggunakan indeks bitmask. Ini akan memberi Anda O (n) kinerja. Alternatif lain adalah menggunakan hash dan mencarinya. Untuk koleksi kecil biasanya jauh lebih baik untuk melakukan pengurutan atau indeks bitmask. Hashtable memiliki kelemahan lokalitas yang lebih buruk jadi ingatlah itu. Sekali lagi, itu hanya jika Anda tidak t peduli duplikat. Jika Anda ingin menghitung duplikat, lanjutkan dengan menyortir keduanya.


sumber
0

Dalam banyak kasus satu-satunya jawaban yang cocok adalah salah satu dari Igor Ostrovsky, jawaban lain didasarkan pada kode hash objek. Tetapi ketika Anda menghasilkan kode hash untuk objek Anda melakukannya hanya berdasarkan bidang IMMUTABLE-nya - seperti bidang Id objek (dalam kasus entitas database) - Mengapa penting untuk mengganti GetHashCode ketika metode Equals ditimpa?

Ini berarti, bahwa jika Anda membandingkan dua koleksi, hasilnya mungkin benar dari metode perbandingan meskipun bidang item yang berbeda tidak sama. Untuk membandingkan jauh koleksi, Anda harus menggunakan metode Igor dan mengimplementasikan IEqualirity.

Silakan baca komentar saya dan mr.Schnider di pos yang paling banyak dipilihnya.

James

James Roeiter
sumber
0

Mengizinkan duplikat di IEnumerable<T>(jika set tidak diinginkan \ mungkin) dan "mengabaikan pesanan" Anda harus dapat menggunakan a .GroupBy().

Saya bukan ahli dalam pengukuran kompleksitas, tetapi pemahaman dasar saya adalah bahwa ini harus O (n). Saya mengerti O (n ^ 2) berasal dari melakukan operasi O (n) di dalam operasi O (n) lainnya seperti ListA.Where(a => ListB.Contains(a)).ToList(). Setiap item di ListB dievaluasi untuk kesetaraan terhadap setiap item di ListA.

Seperti yang saya katakan, pemahaman saya tentang kompleksitas terbatas, jadi perbaiki saya jika saya salah.

public static bool IsSameAs<T, TKey>(this IEnumerable<T> source, IEnumerable<T> target, Expression<Func<T, TKey>> keySelectorExpression)
    {
        // check the object
        if (source == null && target == null) return true;
        if (source == null || target == null) return false;

        var sourceList = source.ToList();
        var targetList = target.ToList();

        // check the list count :: { 1,1,1 } != { 1,1,1,1 }
        if (sourceList.Count != targetList.Count) return false;

        var keySelector = keySelectorExpression.Compile();
        var groupedSourceList = sourceList.GroupBy(keySelector).ToList();
        var groupedTargetList = targetList.GroupBy(keySelector).ToList();

        // check that the number of grouptings match :: { 1,1,2,3,4 } != { 1,1,2,3,4,5 }
        var groupCountIsSame = groupedSourceList.Count == groupedTargetList.Count;
        if (!groupCountIsSame) return false;

        // check that the count of each group in source has the same count in target :: for values { 1,1,2,3,4 } & { 1,1,1,2,3,4 }
        // key:count
        // { 1:2, 2:1, 3:1, 4:1 } != { 1:3, 2:1, 3:1, 4:1 }
        var countsMissmatch = groupedSourceList.Any(sourceGroup =>
                                                        {
                                                            var targetGroup = groupedTargetList.Single(y => y.Key.Equals(sourceGroup.Key));
                                                            return sourceGroup.Count() != targetGroup.Count();
                                                        });
        return !countsMissmatch;
    }
Josh Gust
sumber
0

Solusi sederhana ini memaksa IEnumerabletipe generik untuk diimplementasikan IComparable. Karena OrderBydefinisi.

Jika Anda tidak ingin membuat asumsi seperti itu tetapi masih ingin menggunakan solusi ini, Anda dapat menggunakan potongan kode berikut:

bool equal = collection1.OrderBy(i => i?.GetHashCode())
   .SequenceEqual(collection2.OrderBy(i => i?.GetHashCode()));
Jo Ham
sumber
0

Jika membandingkan untuk tujuan Asertions Pengujian Unit, mungkin masuk akal untuk membuang beberapa efisiensi keluar jendela dan cukup mengkonversi setiap daftar ke representasi string (csv) sebelum melakukan perbandingan. Dengan begitu, pesan Pernyataan pengujian standar akan menampilkan perbedaan dalam pesan kesalahan.

Pemakaian:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

Assert.Equal(collection1.OrderBy(c=>c).ToCsv(), collection2.OrderBy(c=>c).ToCsv());

Metode Ekstensi Pembantu:

public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,
    string joinSeparator = ",")
{
    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) ||
            typeof(T) == typeof(Int32) ||
            typeof(T) == typeof(Int64))
        {
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}
crokusek
sumber