2009-02-06 7 views
46

Voglio ottenere i valori distinti in una lista, ma non dal confronto standard di uguaglianza.Perché non esiste un metodo Linq per restituire valori distinti da un predicato?

Quello che voglio fare è qualcosa di simile:

return myList.Distinct((x, y) => x.Url == y.Url); 

Non posso, non c'è nessun metodo di estensione in LINQ che farà questo - solo uno che prende un IEqualityComparer.

posso hack in giro con questo:

return myList.GroupBy(x => x.Url).Select(g => g.First()); 

ma che sembra disordinato. Inoltre non fa esattamente la stessa cosa - posso solo usarlo qui perché ho una sola chiave.

Potrei anche aggiungere la mia:

public static IEnumerable<T> Distinct<T>( 
    this IEnumerable<T> input, Func<T,T,bool> compare) 
{ 
    //write my own here 
} 

Ma questo sembra un po 'come scrivere qualcosa che dovrebbe essere lì, in primo luogo.

Qualcuno sa perché questo metodo non è presente?

mi sto perdendo qualcosa?

risposta

50

È fastidioso, certamente. Fa parte anche del mio progetto "MoreLINQ" a cui devo prestare attenzione ad un certo punto :) Ci sono molte altre operazioni che hanno senso quando si agisce su una proiezione, ma restituendo l'originale: mi vengono in mente MaxBy e MinBy.

Come dici tu, è facile scrivere - anche se preferisco il nome "DistinctBy" per abbinare OrderBy ecc Ecco la mia esecuzione se siete interessati:

public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
     (this IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector) 
    { 
     return source.DistinctBy(keySelector, 
           EqualityComparer<TKey>.Default); 
    } 

    public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
     (this IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector, 
     IEqualityComparer<TKey> comparer) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException("source"); 
     } 
     if (keySelector == null) 
     { 
      throw new ArgumentNullException("keySelector"); 
     } 
     if (comparer == null) 
     { 
      throw new ArgumentNullException("comparer"); 
     } 
     return DistinctByImpl(source, keySelector, comparer); 
    } 

    private static IEnumerable<TSource> DistinctByImpl<TSource, TKey> 
     (IEnumerable<TSource> source, 
     Func<TSource, TKey> keySelector, 
     IEqualityComparer<TKey> comparer) 
    { 
     HashSet<TKey> knownKeys = new HashSet<TKey>(comparer); 
     foreach (TSource element in source) 
     { 
      if (knownKeys.Add(keySelector(element))) 
      { 
       yield return element; 
      } 
     } 
    } 
+0

Grazie per la rapida risposta - Potrei usarlo! Qualche idea sul perché hanno saltato tutti questi ... Con (Predicato) metodi? – Keith

+0

Non proprio, temo. Inserirò un blog sul progetto MoreLinq quando avrò un insieme significativo di funzionalità ... fondamentalmente sarà un progetto open source con estensioni a LINQ to Objects, e probabilmente anche Push LINQ. –

+7

Se dovessi indovinare, indovinerei per parità con le opzioni IQueryable e cosa è realistico (senza ammalarmi) in TSQL. Quindi DISTINCT (table.column) va bene, ma avresti bisogno di una chiave maneggevole e di un TSQL più complesso per DistinctBy ... –

31

Ma che sembra disordinato.

Non è un problema, è corretto.

  • Se si desidera i programmatori Distinct di FirstName e ci sono quattro David, quale si desidera?
  • Se si programmano i programmatori Group per FirstName e si prende lo First, allora è chiaro cosa si desidera fare nel caso di quattro David.

Posso solo usarlo qui perché ho una sola chiave.

Si può fare un multiplo chiave "distinta" con lo stesso schema:

return myList 
    .GroupBy(x => new { x.Url, x.Age }) 
    .Select(g => g.First()); 
+0

Non avevo pensato di usare tipi di anon come quello - è una buona idea (+1) – Keith

+0

Nice'n breve, semplice, pulito: D +1 – Cerbrus

+0

Ognuno ha risposto con le proprie implementazioni di DistinctBy(), ma questo è il solo uno che affronta la vera domanda di PERCHÉ non c'è DistinctBy() in LINQ. Grazie! – josh2112

3

Jon, la soluzione è abbastanza buono. Un piccolo cambiamento però. Non credo che ci sia bisogno di EqualityComparer.Default.Qui è la mia soluzione (naturalmente il punto di partenza era la soluzione Jon Skeet)

public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector) 
    { 
     //TODO All arg checks 
     HashSet<TKey> keys = new HashSet<TKey>(); 
     foreach (T item in source) 
     { 
      TKey key = keySelector(item); 
      if (!keys.Contains(key)) 
      { 
       keys.Add(key); 
       yield return item; 
      } 
     } 
    } 
+2

Non sono sicuro del motivo per cui sarebbe meglio della soluzione di Jon. 'new HashSet ()' utilizzerà 'EqualityComparer .Default' in ogni caso e, a modo tuo, perderai la possibilità di sovrascriverlo (ad esempio se' TKey' è 'stringa' e vuoi insensibilità al maiuscolo/minuscolo). Anche Jon usa il metodo 'HashSet.Add', mentre usi' HashSet.Contains' e poi 'HashSet.Add' - due operazioni. Certo, avresti bisogno di un set enorme per notare la differenza, ma perché renderlo più lento? – Keith

0

Utilizzando s answer@DavidB', ho scritto un piccolo metodo di estensione DistinctBy, per consentire un predicato per essere passato:

/// <summary> 
/// Distinct method that accepts a perdicate 
/// </summary> 
/// <typeparam name="TSource">The type of the t source.</typeparam> 
/// <typeparam name="TKey">The type of the t key.</typeparam> 
/// <param name="source">The source.</param> 
/// <param name="predicate">The predicate.</param> 
/// <returns>IEnumerable&lt;TSource&gt;.</returns> 
/// <exception cref="System.ArgumentNullException">source</exception> 
public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
    (this IEnumerable<TSource> source, 
    Func<TSource, TKey> predicate) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 

    return source 
     .GroupBy(predicate) 
     .Select(x => x.First()); 
} 

È ora possibile passare un predicato per raggruppare l'elenco in base:

var distinct = myList.DistinctBy(x => x.Id); 

o gruppo da molteplici proprietà:

var distinct = myList.DistinctBy(x => new { x.Id, x.Title }); 
Problemi correlati