Dot-Net

比較兩個集合的相等性,而不考慮其中項目的順序

  • September 8, 2008

我想比較兩個集合(在 C# 中),但我不確定有效實現這一點的最佳方法。

我已經閱讀了關於Enumerable.SequenceEqual的另一個執行緒,但這並不是我想要的。

就我而言,如果兩個集合都包含相同的項目(無論順序如何),它們將是相等的。

例子:

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

collection1 == collection2; // true

我通常做的是遍歷一個集合的每個項目,看看它是否存在於另一個集合中,然後循環遍歷另一個集合的每個項目,看看它是否存在於第一個集合中。(我首先比較長度)。

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

然而,這並不完全正確,而且它可能不是比較兩個集合是否相等的最有效方法。

我能想到的一個錯誤的例子是:

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

這與我的實現相同。我應該只計算找到每個項目的次數並確保兩個集合中的計數相等嗎?


這些範例使用某種 C#(我們稱其為偽 C#),但可以用任何您希望的語言給出答案,沒關係。

**注意:**為簡單起見,我在範例中使用了整數,但我也希望能夠使用引用類型的對象(它們不能正確地作為鍵,因為只比較對象的引用,而不是內容)。

事實證明,微軟已經在其測試框架中包含了這一點:CollectionAssert.AreEquivalent

評論

如果兩個集合具有相同數量的相同元素,但順序不限,則它們是等價的。如果它們的值相等,則元素相等,而不是如果它們引用同一個對象。

使用反射器,我修改了 AreEquivalent() 背後的程式碼以創建相應的相等比較器。它比現有答案更完整,因為它考慮了空值,實現了 IEqualityComparer 並具有一些效率和邊緣情況檢查。另外,它是微軟:)

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)
           hash ^= (val == null ? 42 : m_comparer.GetHashCode(val));

       return hash;
   }
}

範例用法:

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

或者,如果您只想直接比較兩個集合:

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

最後,您可以使用您選擇的相等比較器:

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

引用自:https://stackoverflow.com/questions/50098