Отличный() с лямбдой?

Правильно, поэтому у меня есть перечисляемый и желаю получить от него отличные значения.

Используя System.Linq, конечно, существует метод расширения, называемый Distinct. В простом случае его можно использовать без параметров, например:

var distinctValues = myStringList.Distinct();

Ну и хорошо, но если у меня есть перечисляемые объекты, для которых мне нужно указать равенство, единственная доступная перегрузка:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Аргумент сопоставления равенства должен быть экземпляром IEqualityComparer<T>. Я могу это сделать, конечно, но это несколько многословно и, ну, cludgy.

То, что я ожидал бы, это перегрузка, которая будет принимать лямбда, скажем, Func < T, T, bool > :

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Кто-нибудь знает, существует ли такое расширение, или какое-то эквивалентное обходное решение? Или я что-то упускаю?

В качестве альтернативы, есть способ указать IEqualityComparer встроенный (embarass me)?

Обновление

Я нашел ответ Андерса Хейлсберга на сообщение на форуме MSDN по этому вопросу. Он говорит:

Проблема, с которой вы столкнетесь, заключается в том, что при сравнении двух объектов они должны иметь одинаковое возвращаемое значение GetHashCode (или хэш-таблица, используемая внутренне с помощью Distinct, будет работать неправильно). Мы используем IEqualityComparer, потому что он совместим с пакетами реализации Equals и GetHashCode в один интерфейс.

Я полагаю, что это имеет смысл.

Ответ 1

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

Ответ 2

Мне кажется, что вы хотите DistinctBy из MoreLINQ. Затем вы можете написать:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Здесь вырезанная версия DistinctBy (без проверки недействительности и без возможности указать свой собственный сопоставитель ключей):

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
     (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> knownKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

Ответ 3

Нет такой перегрузки для этого метода расширения. Я обнаружил, что это разочаровывает себя в прошлом, и поэтому я обычно пишу вспомогательный класс для решения этой проблемы. Целью является преобразование a Func<T,T,bool> в IEqualityComparer<T,T>.

Пример

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

Это позволяет вам написать следующие

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

Ответ 4

Обернуть вещи вверх. Я думаю, что большинство людей, которые пришли сюда, как я, хотят, чтобы простейшее решение было без использования каких-либо библиотек и с наилучшей производительностью.

(Принятая группа по методу для меня, я думаю, является излишним с точки зрения производительности.)

Вот простой метод расширения, использующий интерфейс IEqualityComparer, который также работает для нулевых значений.

Применение:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Код метода расширения

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}

Ответ 5

Это сделает то, что вы хотите, но я не знаю о производительности:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

По крайней мере, это не многословно.

Ответ 6

Сокращенное решение

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

Ответ 7

Вот простой метод расширения, который делает то, что мне нужно...

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

Стыдно, что они не выпекали в рамку отличный метод, подобный этому, но hey ho.

Ответ 8

Что-то я использовал, который хорошо работал у меня.

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

Ответ 9

Все решения, которые я видел здесь, зависят от выбора уже сопоставимого поля. Если нужно сравнить по-другому, тем не менее, это решение здесь, похоже, работает в целом, для чего-то вроде:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

Ответ 10

Вы можете использовать InlineComparer

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

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

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

Пример использования:

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

Источник: fooobar.com/questions/13247/...
Использование IEqualityComparer for Union
Могу ли я указать мой встроенный компаратор встроенного типа?

Ответ 11

Я предполагаю, что у вас есть IEnumerable, и в вашем делете делегата вы хотели бы, чтобы c1 и c2 ссылались на два элемента в этом списке?

Я считаю, что вы могли бы добиться этого с помощью самостоятельного присоединения var distinctResults = from c1 в myList                     join c2 в myList на

Ответ 12

Если Distinct() не дает уникальных результатов, попробуйте следующее:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);

Ответ 13

Microsoft System.Interactive package имеет версию Distinct, которая принимает ключевой селектор lambda. Это фактически то же самое, что и решение Jon Skeet, но это может быть полезно для людей, чтобы узнать, и проверить остальную библиотеку.

Ответ 14

IEnumerable расширение лямбда:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

Использование:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 

Ответ 15

Сложный способ сделать это - использовать расширение Aggregate(), используя словарь в качестве аккумулятора с ключами-значениями в качестве ключей:

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

И решение в стиле GroupBy использует ToLookup():

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

Ответ 16

Вот как вы можете это сделать:

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

Этот метод позволяет вам использовать его, указав один параметр, например .MyDistinct(d => d.Name), но также позволяет указать условие наличия в качестве второго параметра следующим образом:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

N.B. Это также позволит вам указать другие функции, например, .LastOrDefault(...).


Если вы хотите выставить только это условие, вы можете сделать это еще проще, выполнив его как:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

В этом случае запрос будет выглядеть так:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

N.B. Здесь выражение проще, но примечание .MyDistinct2 использует .FirstOrDefault(...) неявно.


Примечание. В приведенных выше примерах используется следующий демонстрационный класс

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};

Ответ 17

Иметь другой способ:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

Последовательность возвращает отдельные элементы, сравнивая их по свойству "_myCaustomerProperty".