让我们假设两个对象集合。我想检索第一个集合中未包含在第二个集合中的对象。
对于原始类型的集合,这很简单:
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()
加定制IEqualityComparer<>
,沿着这些思路:
-
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), as it uses a Set<>
.
- Sql 中的“left-outer-join-filtered-by-NULL”技巧怎么样?我没有找到对此的引用,所以要么是我搜索不够,要么是它有缺陷。
-
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()
的良好替代品吗? ?
最佳答案
您可以使用ExceptBy
moreLINQ 库的扩展方法
它允许您指定用于比较的键:
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector)
甚至指定相等比较器:
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer)
关于.net - Linq - 更好的 `Enumerable.Except()` 运算符(性能和灵活性)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17167464/