İçindeki öğelerin sırasına bakılmaksızın iki koleksiyonun eşitlik açısından karşılaştırılması


162

(C #) iki koleksiyon karşılaştırmak istiyorum, ama bunu verimli bir şekilde uygulamak için en iyi yolu emin değilim.

Enumerable.SequenceEqual ile ilgili diğer konuyu okudum , ama tam olarak aradığım şey değil.

Benim durumumda, her ikisi de aynı öğeleri içeriyorsa (koleksiyon ne olursa olsun) iki koleksiyon eşit olacaktır.

Misal:

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

collection1 == collection2; // true

Genellikle yaptığım şey, bir koleksiyonun her öğesinde döngü ve diğer koleksiyonda olup olmadığını görmek, sonra diğer koleksiyonun her öğe arasında döngü ve ilk koleksiyonunda olup olmadığını görmek için. (Uzunlukları karşılaştırarak başlarım).

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

Ancak, bu tamamen doğru değildir ve muhtemelen iki koleksiyonu eşitlik için karşılaştırmanın en etkili yolu değildir.

Bunun yanlış olacağını düşünebileceğim bir örnek:

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

Bu benim uygulamamla eşit olurdu. Her bir öğenin kaç kez bulunduğunu saymalı ve her iki koleksiyonda da sayımların eşit olduğundan emin olmalı mıyım?


Örnekler bir çeşit C # (pseudo-C # diyelim), ancak dilediğiniz dilde cevabınızı verin, önemli değil.

Not: Basitlik için örneklerde tamsayılar kullandım, ancak referans tipi nesneleri de kullanabilmek istiyorum (anahtar olarak doğru davranmıyorlar çünkü içerik değil, yalnızca nesnenin referansı karşılaştırılıyor).


1
Algoritmaya ne dersiniz? Tüm cevap bir şey karşılaştırmak, genel listeler linq vb karşılaştırmak. Gerçekten biz asla eski moda bir programcı olarak algoritma kullanmayacağım birine söz verdi?
Nuri YILMAZ

Eşitliği kontrol ettiğiniz Eşitliği kontrol etmiyorsunuz. Nitpicky ama önemli bir ayrım. Ve uzun zaman önce. Bu iyi bir Q + A.
CAD bloke

Aşağıda açıklanan sözlük tabanlı yöntemin ayarlanmış bir sürümünü tartışan bu gönderiyle ilgilenebilirsiniz . En basit sözlük yaklaşımlarıyla ilgili bir sorun, .NET'in Dictionary sınıfı null anahtarlara izin vermediği için null'ları düzgün işlememeleri.
ChaseMedallion

Yanıtlar:


112

Microsoft'un test çerçevesi kapsamında zaten olduğu ortaya çıktı: CollectionAssert.AreEquivalent

Uyarılar

İki koleksiyon, aynı öğelere aynı miktarda, ancak herhangi bir sırada sahip olmaları durumunda eşdeğerdir. Öğeler, değerleri aynı nesneye başvuruyorsa değil, eşitse eşittir.

Reflektör kullanarak, karşılık gelen bir eşitlik karşılaştırıcısı oluşturmak için AreEquivalent () arkasındaki kodu değiştirdim. Boş yanıtları dikkate aldığı, IEqualityComparer uyguladığı ve bazı verimlilik ve uç durum denetimlerine sahip olduğu için mevcut cevaplardan daha eksiksizdir. artı, bu 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;
    }
}

Örnek kullanım:

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

Veya yalnızca iki koleksiyonu doğrudan karşılaştırmak istiyorsanız:

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

Son olarak, seçtiğiniz bir eşitlik karşılaştırıcınızı kullanabilirsiniz:

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

7
% 100 emin değilim ama cevabınızın Microsoft'un tersine mühendislik kullanım şartlarını ihlal ettiğini düşünüyorum.
Ian Dallas

1
Merhaba Ohad, Lütfen konudaki aşağıdaki uzun tartışmayı okuyun, stackoverflow.com/questions/371328/… Nesne karma kodunu değiştirirseniz, bir karma kümesinde iken, karma doğru eylemiyle kesintiye uğrar ve bir istisnaya neden olabilir. Kural aşağıdaki gibidir: İki nesne eşitse - aynı karma koduna sahip olmalıdırlar. İki nesnenin aynı karma kodu varsa - eşit olmaları şart değildir. Hashcode nesnenin ömrü boyunca aynı kalmalıdır! Bu yüzden ICompareable ve IEqualrity'yi püskürttünüz.
James Roeiter

2
@JamesRoeiter Belki de yorumum yanıltıcıydı. Bir sözlük zaten içerdiği bir hashcode ile karşılaştığında, gerçek eşitliği bir ile kontrol eder EqualityComparer(sağladığınız EqualityComparer.Defaultkodla ya da bunu doğrulamak için Reflektör'ü veya referans kaynağını kontrol edebilirsiniz). Doğru, eğer bu yöntem çalışırken nesneler değişirse (ve özellikle hashcode değişiklikleri), sonuçlar beklenmedikse de, bu sadece bu yöntemin bu bağlamda iş parçacığı için güvenli olmadığı anlamına gelir.
Ohad Schneider

1
@JamesRoeiter Diyelim ki x ve y karşılaştırmak istediğimiz iki nesne. Farklı karma kodları varsa, farklı olduklarını biliyoruz (çünkü eşit öğelerin eşit karma kodları vardır) ve yukarıdaki uygulama doğrudur. Aynı karma koda sahiplerse, sözlük uygulaması belirtilen (veya hiçbiri belirtilmemişse) kullanarak gerçek eşitliği kontrol eder ve yine uygulama doğrudur. EqualityComparerEqualityComparer.Default
Ohad Schneider

1
@CADbloke yöntem arabirim Equalsnedeniyle adlandırılmalıdır IEqualityComparer<T>. Bakmanız gereken, karşılaştırıcının kendisinin adıdır . Bu durumda MultiSetComparermantıklı.
Ohad Schneider

98

Basit ve oldukça verimli bir çözüm, her iki koleksiyonu da sıralamak ve daha sonra eşitlik için karşılaştırmaktır:

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

Yukarıdaki çözüm O (N ^ 2) iken bu algoritma O (N * logN) 'dir.

Koleksiyonlar belirli özelliklere sahipse, daha hızlı bir çözüm uygulayabilirsiniz. Örneğin, her iki koleksiyonunuz da karma kümelerse, kopyalar içeremezler. Ayrıca, bir karma kümesinin bazı öğeler içerip içermediğini kontrol etmek çok hızlıdır. Bu durumda, sizinkine benzer bir algoritma muhtemelen en hızlı olur.


1
Sadece bir System.Linq kullanarak eklemeniz gerekir; çalışması için ilk
Junior Mayhé

bu kod bir döngü içindeyse ve collection1 güncellenirse ve collection2 dokunulmamışsa, her iki koleksiyon da aynı nesneye sahip olsa bile, hata ayıklayıcı bu "eşit" değişken için false gösterir.
Junior Mayhé

5
@Chaulky - OrderBy'nin gerekli olduğuna inanıyorum. Bakınız: dotnetfiddle.net/jA8iwE
Brett

Diğer cevap "yukarıda" olarak adlandırıldı? Muhtemelen stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs

32

Bir Sözlük "dict" oluşturun ve sonra ilk koleksiyondaki her üye için, dict [member] ++;

Sonra, aynı şekilde ikinci koleksiyon üzerinde döngü, ama her üye için [üye] - dikte.

Sonunda, sözlükteki tüm üyelerin üzerine gelin:

    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;

    }

Düzenleme: Bildiğim kadarıyla bu en verimli algoritma ile aynı sırada olduğunu söyleyebilirim. Bu algoritma O (N) olup, Sözlüğün O (1) aramaları kullandığını varsayar.


Neredeyse istediğim bu. Ancak, tamsayı kullanmasam da bunu yapabilmek istiyorum. Referans nesneleri kullanmak istiyorum, ancak sözlüklerdeki anahtarlar gibi düzgün davranmıyorlar.
mbillard

Mono, Öğeleriniz karşılaştırılabilir değilse sorunuz tartışmalıdır. Sözlük'te anahtar olarak kullanılamazlarsa, kullanılabilir bir çözüm yoktur.
skolima

1
Bence Mono anahtarların sıralanabilir olmadığı anlamına geliyordu. Ancak Daniel'in çözümü açıkça bir ağaç değil, bir hashtable ile uygulanacak şekilde tasarlanmıştır ve bir denklik testi ve bir hash fonksiyonu olduğu sürece çalışacaktır.
erickson

Elbette yardım için seçildi, ancak önemli bir noktayı (cevabımı kapsadığım) eksik olduğu için kabul edilmedi.
mbillard

1
FWIW, son foreach döngüsünü basitleştirebilir ve bununla dönüş beyanını yapabilirsiniz:return dict.All(kvp => kvp.Value == 0);
Tyson Williams

18

Bu karşılaştırma yönteminin (C # 'dan) büyük ölçüde (D.Jennings tarafından etkilenen) genel uygulamasıdır:

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

12
İyi iş, ama Not: 1. Daniel Jennings çözümünün aksine, Bu, O (N) değil, daha çok O (N ^ 2), çünkü çubuk koleksiyonundaki foreach döngüsünün içindeki bulma işlevi; 2. Kodda başka bir değişiklik yapmadan ICollection <T> yerine IEnumerable <T> kabul etme yöntemini genelleştirebilirsiniz
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"- Bu doğru değil. Algoritma yanlış varsayımlara dayanır ve çalışırken, çok verimsizdir.
Antonín Lejsek


7

Eğer kullanırsanız Shouldly , sen İçeren ile ShouldAllBe kullanabilirsiniz.

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

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

Ve son olarak, bir uzantı yazabilirsiniz.

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

GÜNCELLEME

ShouldBe yönteminde isteğe bağlı bir parametre vardır .

collection1.ShouldBe(collection2, ignoreOrder: true); // true

1
Sadece son sürümübool ignoreOrder üzerinde ShouldBe yöntemi bir parametre olduğunu buldum .
Pier-Lionel Sgard

5

DÜZENLEME: Bunun gerçekten sadece setler için çalıştığını söylediğimde fark ettim - yinelenen öğeler içeren koleksiyonlarla düzgün bir şekilde ilgilenmeyecek. Örneğin, {1, 1, 2} ve {2, 2, 1} bu algoritmanın bakış açısından eşit kabul edilecektir. Koleksiyonlarınız ayarlanmışsa (veya eşitlikleri bu şekilde ölçülebilirse), umarım aşağıdakileri faydalı bulursunuz.

Kullandığım çözüm:

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

Linq sözlükteki şeyleri kapakların altında yapar, bu yüzden bu da O (N) 'dir. (Notlar, koleksiyonlar aynı boyutta değilse O (1)).

Daniel tarafından önerilen "SetEqual" yöntemini, Igor tarafından önerilen OrderBy / SequenceEquals yöntemi ve benim önerisini kullanarak bir sağlık kontrolü yaptım. Sonuçlar aşağıda, Igor için O (N * LogN) ve mayın ve Daniel için O (N) gösterilmektedir.

Linq kesişim kodunun sadeliği onu tercih edilen çözüm haline getirdiğini düşünüyorum.

__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

Bu kodla ilgili tek sorun, yalnızca değer türlerini karşılaştırırken veya işaretçileri başvuru türleriyle karşılaştırırken çalışmasıdır. Koleksiyonlarda aynı nesnenin iki farklı örneğine sahip olabilirim, bu yüzden her birinin nasıl karşılaştırılacağını belirleyebilmem gerekir. Bir karşılaştırma temsilcisini kesişim yöntemine iletebilir misiniz?
mbillard

Tabii, bir karşılaştırma temsilcisi atayabilirsiniz. Ancak, eklediğim setlerle ilgili olarak, uygulanabilirliğine önemli bir sınır koyan yukarıdaki sınırlamaya dikkat edin.

Kesişim yöntemi farklı bir koleksiyon döndürür. A = {1,1,2} ve b = {2,2,1} verildiğinde a.Intersect (b) .Count ()! = A.Count, ifadenizin doğru şekilde false döndürmesine neden olur. {1,2} .Count! = {1,1,2} .Count Bkz. Bağlantı [/ link] (Her iki tarafın karşılaştırmadan önce ayrıldığını unutmayın.)
Griffin

5

Tekrarlama ve sipariş olmaması durumunda, sözlük anahtarları olarak koleksiyonlara izin vermek için aşağıdaki EqualityComparer kullanılabilir:

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

İşte kullandığım ToHashSet () uygulaması. Hash kodu algoritması (Jon Skeet yoluyla) Etkili Java geliyor.


Serializable for Compareer sınıfı nedir? : o Ayrıca girişi ISet<T>setler için ifade etmek için değiştirebilirsiniz (kopya yok).
nawfal

@nawfal teşekkürler, Serializable olarak işaretlediğimde ne düşündüğümü bilmiyorum ... Gelince ISet, burada fikir IEnumerablebir set olarak (çünkü IEnumerablebaşlamak için bir var) tedavi etmekti, ancak 0 upvotes üzerinde düşünmek oldu 5 yıl bu en keskin fikir olmayabilir: 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);
}

Çözüm .NET 3.5 ve System.Collections.Genericad alanını gerektirir . Microsoft göre , SymmetricExceptWithbir bir O (n + m) ile işlem, n birinci kümedeki elemanların ve sayısını temsil eden m ikinci eleman sayısını temsil eder. Gerekirse bu işleve her zaman bir eşitlik karşılaştırıcısı ekleyebilirsiniz.


3

Neden kullanmıyorsunuz .Except ()

// 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


2
Exceptyinelenen öğeleri saymak için çalışmaz. {1,2,2} ve {1,1,2} kümeleri için true değerini döndürecektir.
Cristian Diaconescu

@CristiDiaconescu kopyaları kaldırmak için ilk önce bir ".Distinct ()" yapabilirsiniz
Korayem

OP istiyor [1, 1, 2] != [1, 2, 2]. Kullanmak Distinctonları eşit gösterecektir.
Cristian Diaconescu

2

Yinelenen bir tür gönderi, ancak koleksiyonları karşılaştırmak için çözümüme göz atın . Oldukça basit:

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

Orijinal yayın burada .


1

erickson neredeyse haklı: yinelenen sayılarla eşleştirmek istediğiniz için bir Çanta istersiniz . Java'da bu şuna benzer:

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

Eminim C # yerleşik bir Set uygulaması vardır. İlk önce bunu kullanırdım; performans bir sorunsa, her zaman farklı bir Set uygulaması kullanabilirsiniz, ancak aynı Set arayüzünü kullanabilirsiniz.


1

İşte ohadsc'ın cevabının uzantı yöntemi varyantı, biri için yararlı olması durumunda

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

Bu ne kadar iyi performans gösteriyor, herhangi bir fikir?
nawfal

Bunu sadece küçük koleksiyonlar için kullanıyorum, bu yüzden Big-O karmaşıklığı veya kıyaslama yapmayı düşünmedim. Sadece HaveMismatchedElements, O (M * N) 'dir, bu nedenle büyük koleksiyonlar için iyi performans göstermeyebilir.
Eric

Eğer IEnumerable<T>s sorgu ise o zaman arama Count()iyi bir fikir değildir. Ohad'ın orijinal yanıtlayıcılarının olup olmadıklarını kontrol etme yaklaşımı ICollection<T>daha iyi bir fikirdir.
nawfal

1

İşte üzerinde bir gelişmedir bir çözümdür bu bir .

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

0

Bu soruna birçok çözüm var. Kopyaları önemsemiyorsanız, her ikisini de sıralamanız gerekmez. Öncelikle aynı sayıda öğeye sahip olduklarından emin olun. Bundan sonra koleksiyonlardan birini sıralayın. Ardından, sıralanmış koleksiyondaki ikinci koleksiyondaki her öğeyi araştırın. Belirli bir öğeyi bulamazsanız durun ve false değerini döndürün. Bunun karmaşıklığı: - ilk koleksiyonu sıralama: N Günlüğü (N) - her öğeyi ilkinden ilkine arama: NLOG (N) ile eşleştiklerini varsayarak 2 * N * LOG (N) elde edersiniz ve her şeyi ararsınız. Bu, her ikisini de sıralamanın karmaşıklığına benzer. Ayrıca bu, bir fark varsa daha önce durma avantajı sağlar. Bununla birlikte, bu karşılaştırmaya girmeden önce her ikisi de sıralanırsa ve qsort gibi bir şey kullanarak sıralamayı denerseniz, sıralamanın daha pahalı olacağını unutmayın. Bunun için optimizasyonlar var. Öğelerin aralığını bildiğiniz küçük koleksiyonlar için harika olan başka bir alternatif, bir bitmask dizini kullanmaktır. Bu size O (n) performansı verecektir. Başka bir alternatif, bir karma kullanmak ve bakmaktır. Küçük koleksiyonlar için sıralama veya bitmask dizinini yapmak genellikle çok daha iyidir. Hashtable, daha kötü konumun dezavantajına sahiptir, bu yüzden bunu aklınızda bulundurun. Yine, bu sadece t Kopyaları önemseme. Yinelenenleri hesaba katmak istiyorsanız, her ikisini de sıralayarak devam edin.


0

Çoğu durumda tek uygun cevap Igor Ostrovsky'nin cevabıdır, diğer cevaplar nesnelerin karma koduna dayanır. Ancak, bir nesne için karma kodu oluşturduğunuzda, bunu yalnızca IMMUTABLE alanlarına (nesne kimliği alanı (bir veritabanı varlığı durumunda) gibi) dayalı olarak yaparsanız , Equals yöntemi geçersiz kılındığında GetHashCode'u geçersiz kılmak neden önemlidir?

Bu, iki koleksiyonu karşılaştırırsanız, farklı öğelerin alanları eşit olmasa bile, sonuç karşılaştırma yöntemi için geçerli olabilir. Koleksiyonları derinlemesine karşılaştırmak için Igor'un yöntemini kullanmanız ve IEqualirity'yi uygulamanız gerekir.

Lütfen benim ve mr.Schnider'ın en çok oy kullandığı yorumlarını okuyun.

James


0

IEnumerable<T>(Kümeler istenmiyorsa \ olası) ve "sıralamayı yoksay" öğelerinde yinelenmelere izin vermek için bir.GroupBy() .

Karmaşıklık ölçümleri konusunda uzman değilim, ama temel anlayışım bunun O (n) olması gerektiğidir. O (n ^ 2) 'yi, başka bir O (n) işlemi gibi bir O (n) işlemi gerçekleştirmekten geliyor gibi anlıyorumListA.Where(a => ListB.Contains(a)).ToList() . ListeB'deki her öğe, Liste A'daki her bir öğe ile eşitlik açısından değerlendirilir.

Dediğim gibi, karmaşıklık konusundaki anlayışım sınırlı, bu yüzden yanılıyorsam beni düzeltin.

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

0

Bu basit çözüm , IEnumerablegenel türünü uygulamaya zorlar IComparable. OrderByTanımı nedeniyle .

Böyle bir varsayım yapmak istemiyor ancak yine de bu çözümü kullanmak istiyorsanız, aşağıdaki kod parçasını kullanabilirsiniz:

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

0

Birim Test İddiası amacıyla karşılaştırma yapılıyorsa, karşılaştırma yapmadan önce bir miktar verimliliği pencereden atmak ve her listeyi bir dize temsiline (csv) dönüştürmek mantıklı olabilir. Bu şekilde, varsayılan test Onay mesajı hata mesajı içindeki farkları görüntüler.

Kullanımı:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

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

Yardımcı Genişletme Yöntemi:

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