2

让我们假设两个对象集合。我想检索第一个集合中不包含在第二个集合中的对象。

对于原始类型的集合,这很容易:

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()加上 custom 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),因为它使用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()吗?

4

2 回答 2

4

您可以使用ExceptBymoreLINQ 库中的扩展方法

它允许您指定用于比较的键:

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)
于 2013-06-18T12:01:57.737 回答
0

我有一个解决方案,使用除外。

看这个:

public class PropertyEqualityComparer<TObject, TProperty> 
    : IEqualityComparer<TObject>
{
    Func<TObject, TProperty> _selector;
    IEqualityComparer<TProperty> _internalComparer;
    public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector,
        IEqualityComparer<TProperty> innerEqualityComparer = null)
    {
        _selector = propertySelector;
        _internalComparer = innerEqualityComparer;
    }
    public int GetHashCode(TObject obj)
    {
        return _selector(obj).GetHashCode();
    }
    public bool Equals(TObject x, TObject y)
    {
        IEqualityComparer<TProperty> comparer = 
            _internalComparer ?? EqualityComparer<TProperty>.Default;
        return comparer.Equals(_selector(x), _selector(y));
    }
}
public static class PropertyEqualityComparer
{
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>(Func<TObject, TProperty> propertySelector)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector);
    }
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>
        (Func<TObject, TProperty> propertySelector, 
        IEqualityComparer<TProperty> comparer)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector, comparer);
    }
}

基本上,它的作用是让您拥有一个可以使用选择器进行比较的 IEqualityComparer。然后你可以像这样使用它:

lst1.Except(lst2, PropertyEqualityComparer.GetNew(n => n.Id));

(抱歉代码格式化,在移动设备上。)

于 2013-06-18T12:04:22.817 回答