让我们假设两个对象集合。我想检索第一个集合中不包含在第二个集合中的对象。
对于原始类型的集合,这很容易:
new[]{1,2,3,4}.Except(new[]{2,3}); // => {1, 4}
但是如果我想使用更复杂的结构怎么办?在下面的示例中,我想使用该Id
字段进行比较。
class Person { string Name; int Id ; }
var lst1 = new[]{ new Person("Ann", 1), new Person("Bob", 2) };
var lst2 = new[]{ new Person("Cathy", 3), new Person("Bob", 2) };
好吧,普遍的共识似乎提供了这两种选择:
Enumerable.Except()
加上 customIEqualityComparer<>
,沿着这些思路:
-
class IdComparer: IEqualityComparer<Person> { /* boilerplate Equals(), GetHashCode() */ }
lst1.Except(lst2, new IdComparer())
.Select(p=>p.Name); // => { "Ann" }
这种方法对于定义相等标准很麻烦。
- 使用否定
.Contains()
- 仍然需要IEqualityComparer<>
; 或否定.Any()
- 这允许指定内联条件。
-
from p1 in lst1
where ! lst2.Any(p2 => p1.Id == p2.Id)
select p1.Name; // => { "Ann" }
这更容易使用,但它读起来像“为 lst1 中的每个元素检查 lst2 中的每个元素”,看起来复杂度为 O(M*N)。不确定不同的 Linq 提供商是否(可以)对此进行优化。
复杂性方面,该.Except()
方法的表现要好得多:大约 O(M+N),因为它使用Set<>
.
- Sql 的“left-outer-join-filtered-by-NULLs”技巧怎么样?我没有找到对这个的参考,所以要么我搜索得不够多,要么它有缺陷。
-
from p1 in lst1
join p2 in lst2 on p1.Id equals p2.Id into grp
where ! grp.Any()
select p1.Name; // => { "Ann" }
这允许使用字段轻松进行比较。
此外,据我所知(深入研究Enumerable.JoinIterator()
实现),复杂度仍然大致为 O(M+N)。
这是一个很好的替代品Enumerable.Except()
吗?