Distinct () z lambda?

Racja, więc mam liczbę i chcę uzyskać z niej różne wartości.

Używając System.Linq, istnieje oczywiście metoda rozszerzenia o nazwie Distinct. W prostym przypadku może być używany bez parametrów, takich jak:

var distinctValues = myStringList.Distinct();

Dobrze i dobrze, ale jeśli mam liczbę obiektów, dla których muszę określić równość, jedynym dostępnym przeciążeniem jest:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Argument porównujący równość musi być instancją IEqualityComparer<T>. Mogę to zrobić, oczywiście, ale jest to nieco gadatliwe i, cóż, cludgy.

Spodziewałbym się przeciążenia, które zajęłoby lambdę, powiedzmy Func:

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

Ktoś wie, czy istnieje jakieś takie rozszerzenie, lub jakieś równoważne obejście? Czy coś przeoczyłem?

Alternatywnie, czy istnieje sposób na określenie Ieequalitycomparer inline (embass me)?

Update

Znalazłem odpowiedź Andersa Hejlsberga na post na forum MSDN na ten temat. Mówi:

Problem, który napotkasz, polega na tym, że gdy dwa obiekty porównują równe muszą mieć taką samą wartość zwracaną GetHashCode (albo tabela hash używana wewnętrznie przez Distinct nie będzie działać poprawnie). Używamy Ieequalitycomparer ponieważ pakuje zgodne implementacje Equals i GetHashCode w jednym interfejsie.

To chyba ma sens..
Author: Enigma State, 2009-08-19

17 answers

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());
 898
Author: Carlo Bos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-21 20:24:14

Wygląda na to, że chcesz DistinctBy z MoreLINQ . Możesz wtedy napisać:

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

Oto skrócona wersja DistinctBy (bez sprawdzania nieważności i bez opcji określania własnego porównywania kluczy):

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;
        }
    }
}
 423
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-22 11:30:50

Aby zakończyć sprawy . Myślę, że większość ludzi, którzy tu przyszli, tak jak ja, chce najprostszego rozwiązania możliwego bez użycia bibliotek i z najlepszą możliwą wydajnością.

(przyjęta Grupa według metody jest dla mnie przesadą pod względem wydajności. )

Oto prosta metoda rozszerzenia wykorzystująca interfejs IEqualityComparer , który działa również dla null wartości.

Użycie:

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

Kod Metody Rozszerzenia

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();
    }
}
 20
Author: Anestis Kivranoglou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-09 13:48:08

No nie ma takiego przeciążenia metody rozszerzenia dla tego. Znalazłem to frustrujące siebie w przeszłości i jako takie Zwykle piszę klasę pomocniczą, aby poradzić sobie z tym problemem. Celem jest konwersja Func<T,T,bool> na IEqualityComparer<T,T>.

Przykład

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

Pozwala to na napisanie następujących

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
 19
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-10 19:41:43

Rozwiązanie skrótu

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
 13
Author: Arasu RRK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-24 19:47:16

To zrobi co chcesz, ale nie wiem o wydajności:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();
Przynajmniej to nie jest gadatliwe.
 12
Author: Gordon Freeman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-06 14:41:59

Oto prosta metoda rozszerzenia, która robi to, czego potrzebuję...

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);
    }
}
Szkoda, że nie wstawili takiej metody do frameworka, ale Hej ho.
 9
Author: David Kirkland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-01 20:19:15

Coś, czego używałem, co dobrze dla mnie działało.

/// <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);
    }
}
 4
Author: Kleinux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 14:15:48

Wszystkie rozwiązania, które tutaj widziałem, polegają na wybraniu już porównywalnego pola. Jeśli jednak trzeba porównać w inny sposób, To rozwiązanie tutaj wydaje się działać ogólnie, dla czegoś takiego jak:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
 3
Author: Dmitry Ledentsov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-14 15:07:26

Możesz użyć 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);
    }
}

Przykład użycia :

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

Źródło: https://stackoverflow.com/a/5969691/206730
Korzystanie z Ieequalitycomparer dla Unii
Czy Mogę określić w wierszu mój jawny komparator typu?

 2
Author: Kiquenet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:36

Trudnym sposobem na to jest użycie rozszerzenia Aggregate(), używając słownika jako akumulatora z key-property wartości jako kluczy:

var customers = new List<Customer>();

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

I GroupBy-style rozwiązanie jest za pomocą ToLookup():

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
 1
Author: Arturo Menchaca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-07 21:43:47

Take another way:

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

Sekwencja zwraca różne elementy porównując je według właściwości '_myCaustomerProperty'.

 1
Author: Bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 07:56:12

Zakładam, że masz liczbę mnogą, a w twoim przykładzie chciałbyś, aby c1 i c2 odnosiły się do dwóch elementów na tej liście?

Wierzę, że można to osiągnąć poprzez samo dołączenie var distinctResults = from c1 in myList dołącz do c2 w myList na

 0
Author: MattH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 14:10:07

Jeśli Distinct() nie daje unikalnych wyników, spróbuj tego:

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);
 0
Author: Andy Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-07 16:09:59

System Microsoftu.Pakiet interaktywny ma wersję Distinct, która pobiera klucz selektora lambda. Jest to faktycznie takie samo rozwiązanie jak rozwiązanie Jona Skeeta, ale może być pomocne dla ludzi, aby wiedzieć i sprawdzić resztę biblioteki.

 0
Author: Niall Connaughton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-16 05:38:53

Oto jak możesz to zrobić:

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

Ta metoda pozwala na jej użycie poprzez podanie jednego parametru, jak .MyDistinct(d => d.Name), ale pozwala również określić warunek having jako drugi parametr, jak tak:

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. Pozwala to również na określenie innych funkcji, takich jak na przykład .LastOrDefault(...).


Jeśli chcesz ujawnić tylko warunek, możesz mieć go jeszcze prostsze, implementując go jako:

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

W tym przypadku zapytanie tylko wygląd:

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

N. B. tutaj wyrażenie jest prostsze, ale uwaga .MyDistinct2 używa .FirstOrDefault(...) w domyśle.


Uwaga: powyższe przykłady używają następującej klasy demo

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
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 15:16:49

IEnumerable rozszerzenie lambda:

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

Użycie:

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
Author: Shantanu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 20:14:49