2013-06-18 3 views
2

Предположим, что имеются две коллекции объектов. Я хочу получить объекты в первой коллекции, которые не содержатся во второй коллекции.Linq - лучше `Enumerable.Except()` оператор (производительность и гибкость)?

Для коллекций примитивных типов, которые легко:

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" } 

Этот метод является громоздким для определения равенства c riteria.

  • с использованием отрицания .Contains() - по-прежнему требуется IEqualityComparer<>; или с отрицанием .Any() - это позволяет указать условие inline.

-

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<>.

  • Как насчет трюка «left-outer-join-filter-by-NULLs» из Sql? Я не нашел ссылок на это, так что либо я не искал достаточно, либо это было испорчено.

-

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()?

+2

Вы можете написать простое проектирование '' EqualityComparer так что вам нужно только написать шаблонные один раз. – CodesInChaos

+0

Как насчет переопределения методов 'Equals' и' GetHashCode' вашего класса? –

+0

@CodesInChaos True - но метод «внешнего соединения» даже позволяет работать с коллекциями разных типов, если существует общий «ключ», а шаблон - 0 –

ответ

4

Вы можете использовать ExceptBy метод расширения из библиотеки moreLINQ

Она позволяет указать ключ, используемый для Comparision:

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) 
+0

Nice :) Вот обзор [добавленных операторов] (https://code.google.com/p/morelinq/wiki/OperatorsOverview) ('ExceptBy()' скрытно отсутствует, поэтому я предполагаю, что документация немного устарела) –

+0

Итак, как это реализовано? Что такое O()? –

0

I У меня есть решение, используя Except.

Посмотрите на это:

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)); 

(извините за форматирование кода, на мобильном телефоне.)

-1

Если у вас есть общий компаратор равенства вы можете использовать, чтобы создать компараторы легко:

public class GenericEqualityComparer<T> : IEqualityComparer<T> 
{ 
    private Func<T, T, Boolean> _comparer; 
    private Func<T, int> _hashCodeEvaluator; 
    public GenericEqualityComparer(Func<T, T, Boolean> comparer) 
    { 
     _comparer = comparer; 
    } 

    public GenericEqualityComparer(Func<T, T, Boolean> comparer, Func<T, int> hashCodeEvaluator) 
    { 
     _comparer = comparer; 
     _hashCodeEvaluator = hashCodeEvaluator; 
    } 

    #region IEqualityComparer<T> Members 

    public bool Equals(T x, T y) 
    { 
     return _comparer(x, y); 
    } 

    public int GetHashCode(T obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 
     if (_hashCodeEvaluator == null) 
     { 
      return 0; 
     } 
     return _hashCodeEvaluator(obj); 
    } 

    #endregion 
} 

Вы бы затем использовать его как это:

var comparer = new GenericEqualityComparer<T>((x, y) => { return x.ID == y.ID; }); 
var result = listOfStuff.Except(listToRemove, comparer); 

//your example 
var comparer = new GenericEqualityComparer<Person>((x, y) => { return x.Id == y.Id; }); 
var result = listOfStuff.Except(listToRemove, comparer); 

Я также использую это родовое равенство ком Парер, когда я хочу посмотреть на определенных ценностях в .Distict()

var comparer = new GenericEqualityComparer<Person>((x, y) => { return x.Name == y.Name; }); 
var distinctNames = listOfAllNames.Distict(comparer); 
+0

Код Uour имеет непоследовательный хэш-код по умолчанию и не обрабатывает нуль правильно => сломанный – CodesInChaos

+0

@CodesInChaos не мог бы вы рассказать о хэш-коде? – FRoZeN

+0

Создайте два объекта с тем же идентификатором и используйте 'new GenericEqualityComparer ((x, y) => {return x.ID == y.ID;})'. Он не будет рассматривать их как равные в «Исключении», потому что они имеют разные хэш-коды. – CodesInChaos

Смежные вопросы