确定两个List 对象是否包含相同的值集,即使它们的顺序不同,最好的方法是什么?

我有两个List对象(其中T对于两个对象都是相同的类型),我需要能够确定它们是否包含相同的值集,即使值不是相同的顺序。

对象是否有任何内置机制来完成此任务,或者我是否需要编写自己的算法?

或许,我应该使用不同类型的集合,而不是List

如果我要编写自己的算法,它可能包含以下步骤 – 如果我走这条路线,我会尝试在最终版本中优化它:

  • 这两个集合是否包含相同数量的值? 如果没有返回false。
  • 计算每个值在每个集合中出现的次数,如果计数不相等则返回false。
  • 如果我到达两个集合的末尾而没有值计数的任何不等,则返回true。

我知道这有一些注意事项,比如T必须具有可比性 – 我现在使用默认比较(例如.Equals() ),并为generics类型定义了适当的约束。

根据可用的信息,我怀疑支持重复的最有效的解决方案是

  • 比较两个列表的大小。 如果不相等,则返回false。 如果相等,
  • 对两个列表排序。 如果必须保留原始列表的顺序,请改为对列表进行排序。
  • 比较排序列表的每个位置的元素,如果给定位置的值不相等则返回false。 如果您已经比较了所有元素而没有找到差异,则返回true。

请注意,我假设在此操作期间有足够的内存可用于创建列表的已排序副本(应将订单保留作为要求)。

所以我们从一个简单的SetEquals ,然后从那里开始。 HashSet已经有了这样一个方法的实现,它可以比较两个集合的相等性,所以我们可以创建一个包装器,以便我们可以将它与任何类型的序列一起使用:

 public static bool SetEquals(this IEnumerable first, IEnumerable second, IEqualityComparer comparer = null) { return new HashSet(second, comparer ?? EqualityComparer.Default) .SetEquals(first); } 

接下来,为了说明你有一个包,而不是一个集合,我们可以只取你拥有的两个序列,对它们进行分组,并将其投影到一个包含项目和匹配项目数量的对中。 如果我们为两个集合执行此操作,那么我们可以将这些对象序列作为集合进行比较 ,并查看它们是否设置为相等。 如果密钥计数对序列都设置为相等,那么原始序列是相同的:

 public static bool BagEquals( this IEnumerable first, IEnumerable second) { Func, IEnumerable>> groupItems = sequence => sequence.GroupBy(item => item, (key, items) => new KeyValuePair(key, items.Count())); return groupItems(first) .SetEquals(groupItems(second)); } 

这是CollectionAssert.AreEquivalent的重新实现(参考代码用DotPeek反编译)但是它不是抛出exception而是返回bool。

 public class CollectionMethods { public static bool AreEquivalent(ICollection expected, ICollection actual) { //We can do a few quick tests we can do to get a easy true or easy false. //Is one collection null and one not? if (Object.ReferenceEquals(expected, null) != Object.ReferenceEquals(actual, null)) return false; //Do they both point at the same object? if (Object.ReferenceEquals(expected, actual)) return true; //Do they have diffrent counts? if (expected.Count != actual.Count) return false; //Do we have two empty collections? if (expected.Count == 0) return true; //Ran out of easy tests, now have to do the slow work. int nullCount1; Dictionary elementCounts1 = CollectionMethods.GetElementCounts(expected, out nullCount1); int nullCount2; Dictionary elementCounts2 = CollectionMethods.GetElementCounts(actual, out nullCount2); //One last quick check, do the two collections have the same number of null elements? if (nullCount2 != nullCount1) { return false; } //Check for each element and see if we see them the same number of times in both collections. foreach (KeyValuePair kvp in elementCounts1) { int expectedCount = kvp.Value; int actualCount; elementCounts2.TryGetValue(key, out actualCount); if (expectedCount != actualCount) { return false; } } return true; } private static Dictionary GetElementCounts(ICollection collection, out int nullCount) { Dictionary dictionary = new Dictionary(); nullCount = 0; foreach (object key in (IEnumerable)collection) { if (key == null) { ++nullCount; } else { int num; dictionary.TryGetValue(key, out num); ++num; dictionary[key] = num; } } return dictionary; } }