比较两个集合的相等性,而不考虑其中项目的顺序

Posted

技术标签:

【中文标题】比较两个集合的相等性,而不考虑其中项目的顺序【英文标题】:Comparing two collections for equality irrespective of the order of items in them 【发布时间】:2010-09-08 04:25:08 【问题描述】:

我想比较两个集合(在 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#),但可以用任何您想要的语言给出答案,没关系。

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

【问题讨论】:

算法怎么样?所有答案都通过比较某些东西,通用列表比较 linq 等。我们真的向某人承诺过我们永远不会像老式程序员那样使用算法吗? 您不是在检查平等,而是在检查等价。这很挑剔,但却是一个重要的区别。而且很久以前。这是一个很好的问答。 您可能对this post 感兴趣,它讨论了下面描述的基于字典的方法的调整版本。大多数简单字典方法的一个问题是它们不能正确处理空值,因为 .NET 的 Dictionary 类不允许空键。 【参考方案1】:

基于这个重复问题的answer,以及答案下方的 cmets,以及@brian-genisio answer,我想出了这些:

        public static bool AreEquivalentIgnoringDuplicates<T>(this IEnumerable<T> items, IEnumerable<T> otherItems)
        
            var itemList = items.ToList();
            var otherItemList = otherItems.ToList();
            var except = itemList.Except(otherItemList);
            return itemList.Count == otherItemList.Count && except.IsEmpty();
        

        public static bool AreEquivalent<T>(this IEnumerable<T> items, IEnumerable<T> otherItems)
        
            var itemList = items.ToList();
            var otherItemList = otherItems.ToList();
            var except = itemList.Except(otherItemList);
            return itemList.Distinct().Count() == otherItemList.Count && except.IsEmpty();
        

测试这两个:

        [Test]
        public void collection_with_duplicates_are_equivalent()
        
            var a = new[] 1, 5, 5;
            var b = new[] 1, 1, 5;

            a.AreEquivalentIgnoringDuplicates(b).ShouldBe(true); 
        

        [Test]
        public void collection_with_duplicates_are_not_equivalent()
        
            var a = new[] 1, 5, 5;
            var b = new[] 1, 1, 5;

            a.AreEquivalent(b).ShouldBe(false); 
        

【讨论】:

【参考方案2】:

事实证明,微软已经在其测试框架中包含了这一点:CollectionAssert.AreEquivalent

备注

如果两个集合是等价的 在相同的元素中具有相同的元素 数量,但顺序不限。元素 如果它们的值相等,则它们相等, 如果它们引用同一个对象,则不是。

使用反射器,我修改了 AreEquivalent() 后面的代码以创建相应的相等比较器。它比现有答案更完整,因为它考虑了空值,实现了 IEqualityComparer 并具有一些效率和边缘情况检查。另外,它是 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

最后,您可以使用您选择的相等比较器:

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

【讨论】:

我不能 100% 确定,但我认为您的回答违反了 Microsoft 针对逆向工程的使用条款。 您好 Ohad,请阅读以下主题中的长篇辩论,***.com/questions/371328/… 如果您更改对象哈希码,而它在哈希集中,它将中断哈希集的正确操作并可能导致异常。规则如下:如果两个对象相等 - 它们必须具有相同的哈希码。如果两个对象具有相同的哈希码 - 它们不是必须相等的。哈希码必须在整个对象的生命周期内保持不变!这就是为什么你需要 ICompareable 和 IEqualrity 。 @JamesRoeiter 也许我的评论具有误导性。当字典遇到它已经包含的哈希码时,它会使用EqualityComparer(您提供的那个或EqualityComparer.Default,您可以检查Reflector 或参考源来验证这一点)检查实际相等 .确实,如果在此方法运行时对象发生更改(特别是其哈希码更改),则结果出乎意料,但这仅意味着此方法在此上下文中不是线程安全的。 @JamesRoeiter 假设 x 和 y 是我们想要比较的两个对象。如果它们有不同的哈希码,我们就知道它们是不同的(因为相等的项目有相同的哈希码),上面的实现是正确的。如果它们具有相同的哈希码,则字典实现将使用指定的EqualityComparer(或EqualityComparer.Default,如果未指定)检查实际相等,并且实现再次正确。 @CADbloke 由于IEqualityComparer&lt;T&gt; 接口,该方法必须命名为Equals。您应该查看的是比较器本身的名称。在这种情况下,它是 MultiSetComparer,这是有道理的。【参考方案3】:

如果出于单元测试断言的目的进行比较,则在进行比较之前将一些效率抛到窗外并简单地将每个列表转换为字符串表示 (csv) 可能是有意义的。这样,默认的测试断言消息将显示错误消息中的差异。

用法:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

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

辅助扩展方法:

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

【讨论】:

【参考方案4】:

This simple solution 强制IEnumerable 的泛型类型实现IComparable。因为 OrderBy的定义。

如果您不想做出这样的假设但仍想使用此解决方案,您可以使用以下代码:

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

【讨论】:

【参考方案5】:

允许IEnumerable&lt;T&gt; 中的重复项(如果集合不可取\可能)和“忽略顺序”,您应该可以使用.GroupBy()

我不是复杂度测量方面的专家,但我的初步理解是这应该是 O(n)。我理解 O(n^2) 来自于在另一个 O(n) 操作中执行 O(n) 操作,例如 ListA.Where(a =&gt; ListB.Contains(a)).ToList()。 ListB 中的每个项目都与 ListA 中的每个项目进行相等性评估。

就像我说的,我对复杂性的理解是有限的,如果我错了,请纠正我。

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;
    

【讨论】:

【参考方案6】:

如果您使用Shouldly,您可以将ShouldAllBe 与Contains 一起使用。

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

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

最后,您可以编写扩展程序。

public static class ShouldlyIEnumerableExtensions

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

更新

ShouldBe 方法中存在可选参数。

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

【讨论】:

我刚刚在latest version 上发现,ShouldBe 方法上有一个参数bool ignoreOrder 对 Shouldly 的精彩参考。【参考方案7】:

这是一个对this one 进行改进的解决方案。

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

【讨论】:

【参考方案8】:
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) 
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);

解决方案需要 .NET 3.5 和 System.Collections.Generic 命名空间。 According to Microsoft,SymmetricExceptWith是一个O(n + m)运算,其中n代表第一个集合中的元素个数,m 表示秒中的元素个数。如有必要,您始终可以向此函数添加相等比较器。

【讨论】:

有趣且罕见的事实。感谢您的知识 最佳答案在这里,简洁,正确,快速。应该赞成。【参考方案9】:

在没有重复和没有顺序的情况下,可以使用下面的 EqualityComparer 来允许集合作为字典键:

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;
    

Here 是我使用的 ToHashSet() 实现。 hash code algorithm 来自 Effective Java(来自 Jon Skeet)。

【讨论】:

Serializable for Comparer 类的意义何在? :o 您也可以将输入更改为ISet&lt;T&gt; 以表示它适用于集合(即没有重复)。 @nawfal 谢谢,不知道我在标记它时在想什么 Serializable... 至于ISet,这里的想法是将IEnumerable 视为一个集合(因为你有一个IEnumerable 开头),尽管考虑到 5 年多来的 0 次投票,这可能不是最敏锐的想法:P【参考方案10】:

一个简单且相当有效的解决方案是对两个集合进行排序,然后比较它们是否相等:

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

这个算法是 O(N*logN),而你上面的解决方案是 O(N^2)。

如果集合具有某些属性,您可能能够实现更快的解决方案。例如,如果您的两个集合都是哈希集,则它们不能包含重复项。此外,检查散列集是否包含某些元素非常快。在这种情况下,类似于您的算法可能会是最快的。

【讨论】:

你只需要添加一个 using System.Linq;首先让它发挥作用 如果此代码在循环中并且 collection1 得到更新而 collection2 保持不变,请注意即使两个集合具有相同的对象,调试器也会为这个“相等”变量显示 false。 @Chaulky - 我相信 OrderBy 是必要的。见:dotnetfiddle.net/jA8iwE 另一个被称为“以上”的答案是什么?可能是***.com/a/50465/3195477 ?【参考方案11】:

在许多情况下,唯一合适的答案是 Igor Ostrovsky 之一,其他答案基于对象哈希码。 但是,当您为对象生成哈希码时,您只能基于他的 IMMUTABLE 字段 - 例如对象 Id 字段(在数据库实体的情况下) - Why is it important to override GetHashCode when Equals method is overridden?

这意味着,如果您比较两个集合,即使不同项目的字段不相等,比较方法的结果也可能为真。 要深入比较集合,需要使用 Igor 的方法并实现 IEqualirity。

请阅读我和施奈德先生在他投票最多的帖子上的内容。

詹姆斯

【讨论】:

【参考方案12】:

这是我的 ohadsc 答案的扩展方法变体,以防它对某人有用

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;
    

【讨论】:

这个表现如何,有什么想法吗? 我只将它用于小型集合,所以没有考虑过 Big-O 的复杂性或做过基准测试。单独的 HaveMismatchedElements 是 O(M*N),因此它对于大型集合可能表现不佳。 如果IEnumerable&lt;T&gt;s 是查询,那么调用Count() 不是一个好主意。 Ohad 的原始答案检查它们是否为ICollection&lt;T&gt; 的方法是更好的主意。【参考方案13】:

为什么不使用 .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

【讨论】:

Except 不适用于计算重复项。对于集合 1,2,2 和 1,1,2,它将返回 true。 @CristiDiaconescu 你可以先做一个“.Distinct()”来删除任何重复项 OP 要求 [1, 1, 2] != [1, 2, 2] 。使用Distinct 会使它们看起来相等。【参考方案14】:

各种各样的重复帖子,但check out my solution for comparing collections。很简单:

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

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

原帖here.

【讨论】:

【参考方案15】:

编辑:我一提出就意识到这真的只适用于集合——它不能正确处理具有重复项目的集合。例如 1, 1, 2 和 2, 2, 1 从该算法的角度来看将被视为相等。但是,如果您的集合是集合(或者它们的相等性可以这样衡量),我希望您发现以下内容很有用。

我使用的解决方案是:

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

Linq 在后台做字典的事情,所以这也是 O(N)。 (注意,如果集合的大小不同,则为 O(1)。

我使用 Daniel 建议的“SetEqual”方法、Igor 建议的 OrderBy/SequenceEquals 方法以及我的建议进行了完整性检查。结果如下,Igor 为 O(N*LogN),我的和 Daniel 的为 O(N)。

我认为 Linq 相交代码的简单性使其成为更可取的解决方案。

__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

【讨论】:

这段代码唯一的问题是它只在比较值类型或比较引用类型的指针时才有效。我可以在集合中有同一个对象的两个不同实例,因此我需要能够指定如何比较每个实例。您可以将比较委托传递给 intersect 方法吗? 当然,你可以传递一个比较器委托。但是,请注意上述关于我添加的集合的限制,这严重限制了它的适用性。 Intersect 方法返回一个不同的集合。给定 a = 1,1,2 和 b =2,2,1,a.Intersect(b).Count() != a.Count,这会导致您的表达式正确返回 false。 1,2.Count != 1,1,2.Count 请参阅link[/link](请注意,在比较之前,两边都是不同的。)【参考方案16】:

这个问题有很多解决方案。 如果您不关心重复项,则不必对两者进行排序。首先确保它们具有相同数量的项目。在排序之后的集合之一。然后对排序集合中的第二个集合中的每个项目进行 binsearch。如果您没有找到给定的项目,请停止并返回 false。 这个的复杂性: - 排序第一个集合:NLog(N) - 从第二个到第一个搜索每个项目:NLOG(N) 所以你最终得到 2*N*LOG(N) 假设它们匹配并且你查找所有内容。这类似于对两者进行排序的复杂性。如果有差异,这也可以让您提前停止。 但是,请记住,如果在您进入此比较之前对两者都进行了排序,并且您尝试使用 qsort 之类的东西进行排序,那么排序将更加昂贵。对此进行了优化。 另一种选择是使用位掩码索引,这对于您知道元素范围的小型集合非常有用。这将为您提供 O(n) 性能。 另一种选择是使用散列并查找它。对于小型集合,进行排序或位掩码索引通常要好得多。 Hashtable 的缺点是局部性较差,因此请记住这一点。 同样,这只是在您不关心重复项的情况下。如果您想解决重复问题,请同时对两者进行排序。

【讨论】:

【参考方案17】:

您可以使用Hashset。看SetEquals方法。

【讨论】:

当然,使用 HashSet 假定没有重复,但如果是这样,HashSet 是最好的方法【参考方案18】:

这是我(深受 D.Jennings 影响)比较方法的通用实现(在 C# 中):

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

【讨论】:

干得好,但注意:1. 与 Daniel Jennings 解决方案相比,这不是 O(N) 而是 O(N^2),因为在 foreach 循环中的 find 函数酒吧收藏; 2.您可以将方法概括为接受 IEnumerable 而不是 ICollection 而无需进一步修改代码 The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item" - 这不是真的。该算法基于错误的假设,虽然有效,但效率极低。【参考方案19】:

erickson 几乎是正确的:因为你想匹配重复的数量,你需要一个Bag。在 Java 中,这看起来像:

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

我确信 C# 有一个内置的 Set 实现。我会先使用它;如果性能有问题,您总是可以使用不同的 Set 实现,但使用相同的 Set 接口。

【讨论】:

【参考方案20】:

创建一个字典“dict”,然后对第一个集合中的每个成员,执行 dict[member]++;

然后,以相同的方式遍历第二个集合,但对每个成员执行 dict[member]--.

最后,遍历字典中的所有成员:

    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;

    

编辑:据我所知,这与最有效的算法顺序相同。这个算法是 O(N),假设 Dictionary 使用 O(1) 查找。

【讨论】:

这几乎就是我想要的。但是,即使我不使用整数,我也希望能够做到这一点。我想使用引用对象,但它们不能像字典中的键一样正常工作。 Mono,如果您的项目不可比较,您的问题就没有实际意义。如果它们不能用作字典中的键,则没有可用的解决方案。 我认为 Mono 的意思是键不可排序。但 Daniel 的解决方案显然是用哈希表而不是树来实现的,只要有等价测试和哈希函数就可以工作。 当然赞成寻求帮助,但没有被接受,因为它遗漏了一个重要点(我在回答中提到了这一点)。 FWIW,您可以使用以下命令简化最后一个 foreach 循环和返回语句:return dict.All(kvp =&gt; kvp.Value == 0);

以上是关于比较两个集合的相等性,而不考虑其中项目的顺序的主要内容,如果未能解决你的问题,请参考以下文章

比较引用类型对象的集合是不是相等,忽略集合中项目的顺序

在Java中测试两个JSON对象的相等性忽略子顺序[关闭]

如何在 C++ 中逐个元素地比较两个向量的相等性?

比较两个 JSON 对象,而不考虑其中的元素序列

更改 STL 多重集中两个相等元素的顺序

比较具有不同项目类型的集合