【问题标题】:Is there a built-in method to compare collections?是否有内置方法来比较集合?
【发布时间】:2010-09-07 19:04:18
【问题描述】:

我想在我的 Equals 方法中比较几个集合的内容。我有一个字典和一个 IList。有没有内置方法可以做到这一点?

编辑: 我想比较两个字典和两个 IList,所以我认为相等的含义很清楚 - 如果两个字典包含映射到相同值的相同键,那么它们是相等的。

【问题讨论】:

  • IList不管订单与否?问题模棱两可。
  • Enumerable.SequenceEqualISet.SetEquals 提供此功能的版本。如果您想与订单无关并使用具有重复项的集合,则需要自己滚动。查看this post 中建议的实现
  • 正如下面评论中提到的,对于 99% 的情况,您可以依赖 NUnit/MSTest 方法 CollectionAssert.AreEquivalent
  • @alexlomba87 这个功能值得一提,但是依赖于生产代码的测试程序集有什么不对吗?

标签: c# .net collections


【解决方案1】:

Enumerable.SequenceEqual

通过使用指定的 IEqualityComparer(T) 比较两个序列的元素来确定两个序列是否相等。

您不能直接比较列表和字典,但您可以将字典中的值列表与列表进行比较

【讨论】:

  • 问题在于 SequenceEqual 期望元素的顺序相同。 Dictionary 类在枚举时不保证键或值的顺序,所以如果要使用 SequenceEqual,则必须先对 .Keys 和 .Values 进行排序!
  • @Orion: ...当然,除非您想检测排序差异:-)
  • @schoetbi:为什么要检测不保证顺序的容器中的顺序差异?
  • @schoetbi:用于从 IEnumerable 中取出某个元素。然而,字典并不能保证顺序,所以.Keys.Values 可以按照他们喜欢的任何顺序返回键和值,并且随着字典的修改,该顺序也可能发生变化。我建议您阅读字典是什么,不是什么。
  • MS 的 TestTools 和 NUnit 提供 CollectionAssert.AreEquivalent
【解决方案2】:

正如其他人所建议和指出的,SequenceEqual 是订单敏感的。为了解决这个问题,您可以按键对字典进行排序(这是唯一的,因此排序始终是稳定的),然后使用SequenceEqual。以下表达式检查两个字典是否相等,无论它们的内部顺序如何:

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

编辑: 正如 Jeppe Stig Nielsen 所指出的,某些对象的 IComparer<T> 与其 IEqualityComparer<T> 不兼容,从而产生不正确的结果。当使用带有此类对象的键时,您必须为这些键指定正确的IComparer<T>。例如,对于字符串键(会出现此问题),您必须执行以下操作才能获得正确的结果:

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

【讨论】:

  • 密钥类型不是CompareTo怎么办?你的解决方案会爆炸。如果键类型具有与其默认相等比较器不兼容的默认比较器怎么办? string 就是这种情况,你知道的。例如,这些字典(带有隐式默认相等比较器)将无法通过您的测试(在我知道的所有文化信息下):var dictionary1 = new Dictionary<string, int> { { "Strasse", 10 }, { "Straße", 20 }, }; var dictionary2 = new Dictionary<string, int> { { "Straße", 20 }, { "Strasse", 10 }, };
  • @JeppeStigNielsen:至于IComparerIEqualityComparer 之间的不兼容——我不知道这个问题,非常有趣!我用可能的解决方案更新了答案。关于缺少CompareTo,我认为开发人员应该确保提供给OrderBy() 方法的委托返回类似的东西。我认为这适用于任何用途或OrderBy(),即使在字典比较之外。
【解决方案3】:

除了提到的SequenceEqual,还有哪个

如果两个列表长度相等且它们对应的列表为真 元素根据比较器比较相等

(可能是默认比较器,即覆盖的Equals()

值得一提的是,在.Net4中有SetEqualsISet对象上, 其中

忽略元素的顺序和任何重复的元素。

因此,如果您想要一个对象列表,但它们不需要按特定顺序排列,请考虑ISet(如HashSet)可能是正确的选择。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这是不可靠的,因为 SequenceEqual 期望值以可靠的顺序从字典中出来 - 字典对顺序没有做出这样的保证,dictionary.Keys 很可能以 [2, 1 ] 而不是 [1, 2] 并且您的测试将失败
    【解决方案5】:

    这不是直接回答你的问题,但是 MS 的 TestTools 和 NUnit 都提供了

     CollectionAssert.AreEquivalent
    

    这几乎可以满足您的需求。

    【讨论】:

      【解决方案6】:

      我不知道 Enumerable.SequenceEqual 方法(你每天都会学到一些东西......),但我会建议使用扩展方法;像这样:

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

      有趣的是,在花了 2 秒时间阅读了有关 SequenceEqual 的内容后,看起来微软已经构建了我为您描述的功能。

      【讨论】:

        【解决方案7】:

        .NET 缺乏用于比较集合的任何强大工具。我开发了一个简单的解决方案,您可以在下面的链接中找到:

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

        无论顺序如何,这都会执行相等比较:

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

        这将检查是否添加/删除了项目:

        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
        

        这将看到字典中的哪些项目发生了变化:

        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
        

        【讨论】:

        • 当然 .NET 有强大的工具来比较集合(它们是基于集合的操作)。 .Removedlist1.Except(list2) 相同,.Addedlist2.Except(list1).Equallist1.Intersect(list2).Differentoriginal.Join(changed, left =&gt; left.Key, right =&gt; right.Key, (left, right) =&gt; left.Value == right.Value)。您几乎可以与 LINQ 进行任何比较。
        • 更正:.Differentoriginal.Join(changed, left =&gt; left.Key, right =&gt; right.Key, (left, right) =&gt; new { Key = left.Key, NewValue = right.Value, Different = left.Value == right.Value).Where(d =&gt; d.Different)。如果您还需要旧值,您甚至可以添加 OldValue = left.Value
        • @AllonGuralnek 您的建议很好,但它们不能处理 List 不是真正集合的情况 - 列表多次包含相同的对象。比较 { 1, 2 } 和 { 1, 2, 2 } 不会返回任何添加/删除的内容。
        【解决方案8】:

        要比较集合,您还可以使用 LINQ。 Enumerable.Intersect 返回所有相等的对。你可以像这样比较两个字典:

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

        第一次比较是必要的,因为dict2 可以包含来自dict1 的所有键等等。

        您还可以使用Enumerable.ExceptEnumerable.Union 来考虑变体,从而得出相似的结果。但可用于确定集合之间的确切差异。

        【讨论】:

          【解决方案9】:

          这个例子怎么样:

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

          礼貌:https://www.dotnetperls.com/dictionary-equals

          【讨论】:

          • value != pair.Value 进行参考比较,请改用 Equals
          【解决方案10】:

          对于有序集合(列表、数组)使用SequenceEqual

          对于 HashSet 使用 SetEquals

          对于字典,你可以这样做:

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

          (更优化的解决方案将使用排序,但需要IComparable&lt;TValue&gt;

          【讨论】:

            【解决方案11】:

            不,因为框架不知道如何比较列表的内容。

            看看这个:

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

            【讨论】:

            • 不是已经有几个选项可以告诉框架如何比较元素了吗? IComparer&lt;T&gt;,覆盖object.EqualsIEquatable&lt;T&gt;IComparable&lt;T&gt; ...
            【解决方案12】:
            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;
            }
            

            【讨论】:

              【解决方案13】:

              没有,现在没有,也可能没有,至少我会这么认为。背后的原因是集合相等可能是用户定义的行为。

              集合中的元素不应该按特定顺序排列,尽管它们自然有顺序,但这不是比较算法应该依赖的。假设您有两个集合:

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

              它们是否相等?你一定知道,但我不知道你的观点是什么。

              默认情况下,集合在概念上是无序的,直到算法提供排序规则。 SQL server 会引起你注意的是,当你尝试进行分页时,它需要你提供排序规则:

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

              还有两个系列:

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

              再次,它们是否相等?你告诉我..

              集合的元素可重复性在不同的场景中发挥其作用,并且像Dictionary&lt;TKey, TValue&gt; 这样的一些集合甚至不允许重复元素。

              我相信这些类型的平等是应用程序定义的,因此框架没有提供所有可能的实现。

              好吧,在一般情况下Enumerable.SequenceEqual 已经足够好了,但在以下情况下它会返回 false:

              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
              

              我阅读了一些此类问题的答案(您可以为他们google)以及我一般会使用什么:

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

              这意味着一个集合在其元素中代表另一个集合,包括重复次数,而不考虑原始顺序。实现的一些注意事项:

              • GetHashCode() 只是为了排序而不是为了相等;我认为在这种情况下就足够了

              • Count()不会真正枚举集合,直接落入ICollection&lt;T&gt;.Count的属性实现中

              • 如果引用相同,那就是 Boris

              【讨论】:

                【解决方案14】:

                我制作了自己的比较方法。它返回常见的、缺失的和额外的值。

                private static void Compare<T>(IEnumerable<T> actual, IEnumerable<T> expected, out IList<T> common, out IList<T> missing, out IList<T> extra) {
                    common = new List<T>();
                    missing = new List<T>();
                    extra = new List<T>();
                
                    var expected_ = new LinkedList<T>( expected );
                    foreach (var item in actual) {
                        if (expected_.Remove( item )) {
                            common.Add( item );
                        } else {
                            extra.Add( item );
                        }
                    }
                    foreach (var item in expected_) {
                        missing.Add( item );
                    }
                }
                

                【讨论】:

                  【解决方案15】:

                  没有。集合框架没有任何平等的概念。如果您考虑一下,就无法比较不主观的收藏。例如,将您的 IList 与您的 Dictionary 进行比较,如果所有键都在 IList 中,所有值都在 IList 中,或者两者都在 IList 中,它们是否相等?在不知道它们的用途的情况下,没有明显的方法可以比较这两个集合,因此通用的 equals 方法没有意义。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-04-24
                    • 1970-01-01
                    • 2011-01-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-09-20
                    相关资源
                    最近更新 更多