Отличаться () с лямбда? - PullRequest
       30

Отличаться () с лямбда?

692 голосов
/ 19 августа 2009

Верно, поэтому у меня есть перечислимое и я хочу получить из него различные значения.

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

var distinctValues = myStringList.Distinct();

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

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

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

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

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

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

В качестве альтернативы, есть ли способ указания встроенного IEqualityComparer (смущать меня)?

Обновление

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

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

Полагаю, это имеет смысл ..

Ответы [ 18 ]

2 голосов
/ 13 октября 2014

Вы можете использовать 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);

Источник: https://stackoverflow.com/a/5969691/206730
Использование IEqualityComparer для Union
Могу ли я указать свой явный компаратор типа inline?

1 голос
/ 26 октября 2018

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

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

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

        public int GetHashCode(T obj)
        {
            return obj.GetHashCode();
        }

        private readonly Func<T, T, bool> _equalsFunction;
    }
1 голос
/ 07 апреля 2016

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

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());
0 голосов
/ 26 июля 2016

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

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"}
};
0 голосов
/ 19 августа 2009

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

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

0 голосов
/ 19 июля 2015

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()); 
0 голосов
/ 16 июня 2015

Пакет Microsoft System.Interactive имеет версию Distinct, которая использует лямбда-переключатель ключей. По сути, это то же самое, что и решение Джона Скита, но оно может помочь людям узнать и проверить остальную часть библиотеки.

0 голосов
/ 07 декабря 2011

Если 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);
...