Question Distinct () avec lambda?


C'est vrai, j'ai donc un nombre énumérable et je souhaite en tirer des valeurs distinctes.

En utilisant System.Linq, il y a bien sûr une méthode d'extension appelée Distinct. Dans le cas simple, il peut être utilisé sans paramètres, comme:

var distinctValues = myStringList.Distinct();

Eh bien, mais si j'ai un nombre d'objets pour lequel j'ai besoin de spécifier l'égalité, la seule surcharge disponible est:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

L'argument d'égalité de définition doit être une instance de IEqualityComparer<T>. Je peux le faire, bien sûr, mais c'est un peu verbeux et, bien, cludgy.

Ce que j'aurais attendu est une surcharge qui prendrait un lambda, disons un Func <T, T, bool>:

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

Quelqu'un sait si une telle extension existe, ou une solution de rechange équivalente? Ou est-ce que je manque quelque chose?

Sinon, existe-t-il un moyen de spécifier un IEqualityComparer inline (embarass me)?

Mettre à jour

J'ai trouvé une réponse de Anders Hejlsberg à un poster dans un forum MSDN sur ce sujet. Il dit:

Le problème que vous allez rencontrer est que lorsque deux objets se comparent   égaux, ils doivent avoir la même valeur de retour GetHashCode (ou bien le   table de hachage utilisée en interne par Distinct ne fonctionnera pas correctement).   Nous utilisons IEqualityComparer car il est compatible avec les paquets   implémentations de Equals et GetHashCode dans une interface unique.

Je suppose que cela a du sens ..


625
2017-08-19 13:50


origine


Réponses:


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

878
2017-11-11 19:19



Il me semble que tu veux DistinctBy de PlusLINQ. Vous pouvez alors écrire:

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

Voici une version réduite de DistinctBy (pas de vérification de nullité et aucune option pour spécifier votre propre comparateur de clé):

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

415
2017-08-19 13:55



Pour envelopper les choses . Je pense que la plupart des gens qui sont venus ici comme moi veulent la le plus simple solution possible sans utiliser de bibliothèques et avec le meilleur possible performance.

(Le groupe accepté par méthode pour moi, je pense est une surpuissance en termes de performance.)

Voici une méthode d'extension simple utilisant le IEqualityComparer interface qui fonctionne également pour les valeurs nulles.

Usage:

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

Code de méthode d'extension

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
2017-12-09 13:48



Non, il n'y a pas de surcharge de méthode d'extension pour cela. J'ai trouvé cela frustrant moi-même dans le passé et en tant que tel, j'écris habituellement une classe d'aide pour faire face à ce problème. Le but est de convertir un Func<T,T,bool> à IEqualityComparer<T,T>.

Exemple

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

Cela vous permet d'écrire ce qui suit

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

19
2017-08-19 14:01



Solution raccourcie

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

13
2017-08-20 13:27



Cela fera ce que vous voulez, mais je ne sais pas à propos de la performance:

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

Au moins, ce n'est pas verbeux.


12
2017-10-06 14:41



Voici une méthode d'extension simple qui fait ce dont j'ai besoin ...

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

C'est dommage qu'ils n'aient pas fait une telle méthode dans le cadre, mais bon ho.


9
2018-03-01 20:19



Quelque chose que j'ai utilisé qui a bien fonctionné pour moi.

/// <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
2017-08-19 14:15



Toutes les solutions que j'ai vues ici reposent sur la sélection d'un champ déjà comparable. Si l'on doit comparer d'une manière différente, cependant, cette solution ici semble fonctionner de manière générale, pour quelque chose comme:

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

3
2017-11-14 15:07