我想比较两个集合(用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#),但不管你想用什么语言给出答案,都无关紧要.

Note:为了简单起见,我在示例中使用了整数,但我也希望能够使用引用类型的对象(它们不能正确地作为键,因为只比较对象的引用,而不是内容).

推荐答案

事实证明,微软的测试框架已经涵盖了这一点:CollectionAssert.AreEquivalent

comments

如果两个集合

使用reflector,我修改了AreEquivalent()后面的代码,以创建相应的等式比较器.它比现有答案更完整,因为它考虑了空值,实现了IEqualityComparer,并具有一些效率和边缘 case 判断.加上,它是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)
            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

最后,您可以使用自己 Select 的等式比较器:

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

.net相关问答推荐

与 Datagrid 的 SelectedItem 链接时的 WPF RadioButton 绑定问题

如何在 .net MAUI 中删除不需要编译的平台?

System.IO.Directory.Exists 在 LINQ 语句中失败,但在 foreach 循环中没有

如何计算给定2个字符串的距离相似性度量?

在.NET C#中截断整个单词的字符串

如何在 FtpWebRequest 之前判断 FTP 上是否存在文件

设置日志(log)文件名以在 Log4j 中包含当前日期

为什么 .Contains 慢?通过主键获取多个实体的最有效方法?

是否可以模拟 .NET HttpWebResponse?

ILMerge 最佳实践

C# 有办法给我一个不可变的字典吗?

为什么 LINQ .Where(predicate).First() 比 .First(predicate) 快?

在安全处理异常时避免首次机会异常消息

C# 中的 override 和 new 关键字有什么区别?

强制 XmlSerializer 将 DateTime 序列化为 'YYYY-MM-DD hh:mm:ss'

无法加载文件或程序集Antlr3.Runtime (1)或其依赖项之一

使 HashSet 不区分大小写

如何访问 Session 变量并在 javascript 中设置它们?

在不使用while循环的情况下找到最里面的异常?

Roslyn 编译代码失败