有内置的方法可以比较集合吗?


178

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

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


不论秩序与否IList?问题是模棱两可的。
nawfal

Enumerable.SequenceEqualISet.SetEquals提供此功能的版本。如果要与订单无关,并且要处理具有重复项的集合,则需要自己滚动。看看这篇文章中
ChaseMedallion '16

Answers:


185

Enumerable.SequenceEqual

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

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


52
问题在于SequenceEqual期望元素的顺序相同。字典类不保证枚举时键或值的顺序,因此,如果要使用SequenceEqual,则必须首先对.Keys和.Values进行排序!
Orion Edwards,

3
@Orion:...,除非您想检测订购差异,否则:-)
schoetbi 2010年

30
@schoetbi:为什么要在不能保证订单的容器中检测订购差异?
Matti Virkkunen 2011年

4
@schoetbi:这是为了从IEnumerable中删除某些元素。但词典不保证顺序,因此.Keys.Values可能返回键和值在他们觉得像任何订单,该订单可能变化,因为字典被修改为好。我建议您阅读什么是词典,什么不是词典。
Matti Virkkunen 2011年

5
MS的TestTools和NUnit提供CollectionAssert.AreEquivalent
tymtam

44

正如其他人所建议和指出的那样,它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 }, };
Jeppe Stig Nielsen

@JeppeStigNielsen:至于之间的不兼容性IComparerIEqualityComparer-我不知道这个问题的,非常有趣!我用可能的解决方案更新了答案。关于缺少CompareTo,我认为开发人员应确保提供给该OrderBy()方法的委托返回可比较的内容。我认为OrderBy(),即使在字典比较之外,也可以对或进行任何使用。
Allon Guralnek '16

15

除了上述SequenceEqual之外

如果两个列表的长度相等,并且它们的对应元素根据比较器进行比较,则为true

(它可能是默认的比较器,即重写Equals())。

值得一提的是,在.Net4中ISet对象上存在SetEquals

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

因此,如果您希望有一个对象列表,但它们不必按特定顺序排列,请考虑使用ISet(如HashSet)是正确的选择。


7

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

13
这是不可靠的,因为SequenceEqual期望值以可靠的顺序从字典中出来-字典不对顺序和字典做出任何保证。键很可能以[2,1]而不是[1,2]的形式出现并且您的测试将失败
Orion Edwards

5

.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

10
当然,.NET具有用于比较集合的强大工具(它们是基于集合的操作)。.Removedlist1.Except(list2).Addedis list2.Except(list1).Equalis list1.Intersect(list2).Differentis相同original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value)。您几乎可以使用LINQ进行任何比较。
Allon Guralnek,2011年

3
校正:.Differentoriginal.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)OldValue = left.Value如果您还需要旧值,甚至可以添加。
Allon Guralnek,2011年

3
@AllonGuralnek,您的建议很好,但是它们不能处理List不是真实集合的情况-列表多次包含相同的对象。比较{1,2}和{1,2,2}不会返回添加/删除的内容。
Niall Connaughton


4

我不知道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之后,看来Microsoft已构建了我为您介绍的功能。


4

这不是直接回答您的问题,但是MS的TestTools和NUnit都可以提供

 CollectionAssert.AreEquivalent

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


在我的NUnit测试中寻找它
Blem,

1

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

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

需要进行第一次比较,因为它dict2可以包含from dict1和更多的所有键。

您还可以考虑使用Enumerable.ExceptEnumerable.Union导致相似结果的变化。但是可以用来确定集合之间的确切差异。


1

这个例子怎么样:

 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
kofifus

1

对于有序集合(列表,数组),请使用 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<TValue>


0

不。收集框架没有任何平等的概念。如果您考虑一下,则无法比较不是主观的集合。例如,将您的IList与字典进行比较,如果所有键都在IList中,所有值都在IList中,或者两个值都在IList中,它们是否相等?没有明显的方法来比较这两个集合,而又不知道它们将用于什么用途,因此通用的equals方法毫无意义。



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

0

曾经,现在,也可能不是,至少,我相信。背后的原因是收集平等可能是用户定义的行为。

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

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

他们是否相等?您必须知道,但我不知道您的观点。

默认情况下,集合在概念上是无序的,直到算法提供排序规则为止。SQL Server将引起您注意的同一件事是,当您尝试进行分页时,它要求您提供排序规则:

https://docs.microsoft.com/zh-CN/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<TKey, TValue>甚至不允许重复元素。

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

好吧,通常情况下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

我阅读了一些类似这样的问题的答案(您可能会用谷歌搜索它们)以及我会使用的一般情况:

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<T>.Count

  • 如果引用相等,那只是鲍里斯

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.