比较两个List<T>对象是否相等,忽略顺序

293

又是一个比较列表的问题。

List<MyType> list1;
List<MyType> list2;
我需要检查它们是否拥有相同的元素,而不考虑它们在列表中的位置。每个对象可能会出现在列表中多次。是否有内置的函数可以检查这一点?如果我保证每个元素仅在列表中出现一次呢?
编辑:大家谢谢你们的回答,但我忘记添加一些信息,每个元素的出现次数在两个列表中应该相同。

2
您可能会对这篇文章感兴趣,它展示了如何修复基于字典的解决方案中的空值处理问题,同时提高性能。 - ChaseMedallion
9个回答

375
如果您希望它们完全相等(即相同的项目和每个项目的相同数量),我认为最简单的解决方案是在比较之前进行排序:
Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t))

编辑:

这里有一个性能更好的解决方案(大约快了十倍),只需要IEquatable,而不需要IComparable:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
  var cnt = new Dictionary<T, int>();
  foreach (T s in list1) {
    if (cnt.ContainsKey(s)) {
      cnt[s]++;
    } else {
      cnt.Add(s, 1);
    }
  }
  foreach (T s in list2) {
    if (cnt.ContainsKey(s)) {
      cnt[s]--;
    } else {
      return false;
    }
  }
  return cnt.Values.All(c => c == 0);
}

编辑2:

要处理任何数据类型作为键(例如,像Frank Tzanabetis指出的可空类型),您可以创建一个采用字典比较器的版本:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
  var cnt = new Dictionary<T, int>(comparer);
  ...

4
这是一个不错的回答,我认为它是正确的,而且比我的回答更短。我唯一的建议是使用SequenceEqual作为扩展方法。但我应该指出,这需要TIComparable,而ToLookup版本只需要正确的GetHashCodeEquals实现。 - Ani
3
谢谢您的代码,但使用字典有一个问题-如果T是可空类型,并且传入的集合中有null,则会导致它出错。 另外,也许将方法重命名为ScrambledEggs,因为我第一次看到它时就是这样读的。可能是饿了... - Frank Tzanabetis
1
@Guffa 另一个小优化是在遍历第二个列表时检查 cnt[s]>0。如果不是,您可以立即返回 false - DixonD
3
我知道这个问题很旧了,但是即使您添加了IEqualityComparer参数,如果尝试迭代空列表,你仍会得到空引用异常。您可以在函数开头添加以下代码:if (list1 == null && list2 == null) { return true; } if (list1 == null || list2 == null) { return false; } 这样可以避免空引用异常并保证代码正常运行。 - Jacob
1
如果在创建字典时提供了比较器,那么在比较键时它将使用该比较器。如果比较器处理可空类型,则可以将 null 值用作字典中的一个键。字典不关心键值,它只关心比较器告诉它有关键值的信息。 - Guffa
显示剩余20条评论

56

如果你不关心出现次数,我会这样处理。 使用哈希集比简单迭代更有效率。

var set1 = new HashSet<MyType>(list1);
var set2 = new HashSet<MyType>(list2);
return set1.SetEquals(set2);
这将需要您覆盖 .GetHashCode() 并在 MyType 上实现 IEquatable<MyType>

我只需要在MyType上实现GetHashCode吗? - Bruno Teixeira
1
@recursive:这种方法不会考虑到重复项,正如OP在编辑中所指出的那样。但是如果可以忽略重复项,这种方法仍然有效。 - LBushkin
2
HashSet<T> 还需要您实现 Equals 方法。项目将相互比较,不仅仅是哈希码的比较。 - Guffa
1
@Guffa:这里的其他方法都需要使用Equals - recursive
4
是的,但答案只提到了“GetHashCode”,好像并不需要“Equals”。 - Guffa
3
不需要将set2设置为哈希集,任何可枚举的对象都可以。 - nawfal

51

这个问题本身存在歧义。以下陈述:

无论它们在列表中的位置如何,它们都具有相同的元素。每个MyType对象可能会在列表中出现多次。

并没有说明您是要确保两个列表具有相同的元素集合还是相同的不同元素集合

如果您想确保两个集合具有完全相同且顺序无关紧要的成员,可以使用:

// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();

如果您想确保两个集合具有相同的成员不重复的集合(在其中任意一个中重复的成员将被忽略),则可以使用:

// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();
使用集合操作(Intersect,Union,Except)比使用Contains等方法更有效率。在我看来,它也更好地表达了您查询的期望。 编辑:现在你已经澄清了你的问题,我可以说你想使用第一种形式——因为重复项很重要。这里有一个简单的例子来演示您可以获得所需的结果:
var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };

// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any(); 

33
第一种方法在以下情况下不起作用:a = new[] {1,5,5}b = new[] {1,1,5}。这些集合没有完全相同的成员,但areEquivalent被设置为true - Remko Jansen
2
@Remko Jansen是正确的,使用!a.Except(b).Any()的方法是有问题的 - 它会说a={2,2}和b={1,2}相等。我想知道它怎么能得到这么多票? - Pavel K
1
这个答案没有考虑到计数可以相同,除非匹配但列表仍然可以不同的情况:{ 1,1,2,2,3,3 } != { 1,2,3,4,5,6 }即使以相反的方向执行.Except(...)也无法解决问题:{ 1,1,2,3 } != { 1,2,2,3 } - Josh Gust
在这里,您可以找到我的扩展方法,用于基于此答案的集合比较,修复在此处评论中提到的问题:https://dev59.com/jHVD5IYBdhLWcg3wNY1Z#67486151 - xhafan

15

除了Guffa的回答之外,您还可以使用这个变量来获得更简洁的表示方法。

public static bool ScrambledEquals<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
  var deletedItems = list1.Except(list2).Any();
  var newItems = list2.Except(list1).Any();
  return !newItems && !deletedItems;          
}

2
这个代码可以运行,但如果你想让它尽可能地优化,你可能需要使用 return !(list1.Except(list2).Any()) && !(list2.Except(list1).Any()); (#编辑:该死,我无法使其正常工作。诅咒我的新手水平。) - Ecchi-Alex
1
[1, 1, 2] 和 [2, 2, 1] 返回 true,这是不正确的。 - Twisted Whisper

10

我认为这个应该达到你想要的效果:

list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));

如果您希望它是唯一的,您可以将其更改为:

list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count

5
仍有可能出现假阳性。考虑 {1, 2, 2} 和 {1, 1, 2},它们包含相同的项,计数相同,但仍然不相等。 - Guffa
@Guffa:好观点。我现在明白了,加上list1.Distinct()后。如果所有项都相同,而列表1是不同的,并且它们的长度相同,则列表2也必须是不同的。现在,{1,2}和{2,1}被认为是相同的,但{1,2,2}和{1,1,2}则不是。 - Brian Genisio
2
虽然从技术上讲,Contains() 的行为是正确的,但它可能导致 O(N<sup>2</sup>) 的性能问题。如果项目数量很大,则集合操作(Except、Intersect、Union)的性能要好得多。 - LBushkin
@Brian Genisio:现在对于{1,2,2}和{1,2,2}会得到一个假负结果... - Guffa
@Guffa:你应该这样做。这些列表不是独特的。第二个答案假设这些列表是独特的并且包含相同的值。 - Brian Genisio

9

这是一个稍微有难度的问题,我认为它可以简化为:“测试两个列表是否互为排列。”

我相信其他人提供的解决方案只能指示这两个列表是否包含相同的唯一元素。这是必要但不充分的测试,例如{1, 1, 2, 3}不是{3, 3, 1, 2}的排列,尽管它们的计数相等并且它们包含相同的不同元素。

我认为下面的方法可以解决问题,虽然不是最有效的:

static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
   if(list1.Count != list2.Count)
         return false;

   var l1 = list1.ToLookup(t => t);
   var l2 = list2.ToLookup(t => t);

   return l1.Count == l2.Count 
       && l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count()); 
}

3
这个方法适用于我:
如果您正在比较两个对象列表,并依赖于单个实体(如 ID),并且您想要一个符合该条件的第三个列表,则可以执行以下操作:
var list3 = List1.Where(n => !List2.select(n1 => n1.Id).Contains(n.Id));

Refer: MSDN - C# Compare Two lists of objects


0

我使用这个方法)

public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);

public static bool CompareTwoArrays<T1, T2>(this IEnumerable<T1> array1, IEnumerable<T2> array2, CompareValue<T1, T2> compareValue)
{
    return array1.Select(item1 => array2.Any(item2 => compareValue(item1, item2))).All(search => search)
            && array2.Select(item2 => array1.Any(item1 => compareValue(item1, item2))).All(search => search);
}

-1

试试这个!!!

使用以下代码,您可以比较一个或多个字段以生成符合您需求的结果列表。结果列表将仅包含已修改的项目。

// veriables been used
List<T> diffList = new List<T>();
List<T> gotResultList = new List<T>();



// compare First field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1))).ToList();
// Generate result list
diffList.AddRange(gotResultList);

// compare Second field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2))).ToList();
// Generate result list
diffList.AddRange(gotResultList);


MessageBox.Show(diffList.Count.ToString);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接