Почему нет метода Linq для возврата различных значений по предикату? - PullRequest
49 голосов
/ 06 февраля 2009

Я хочу получить отдельные значения в списке, но не путем стандартного сравнения на равенство.

Что я хочу сделать, это примерно так:

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

Не могу, в Linq нет метода расширения, который бы это делал - только тот, который принимает IEqualityComparer.

Я могу взломать это с помощью:

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

Но это кажется грязным. Это также не совсем то же самое - я могу использовать это только здесь, потому что у меня есть один ключ.

Я также мог бы добавить свой собственный:

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

Но это действительно похоже на написание чего-то, что должно быть там во-первых.

Кто-нибудь знает, почему этот метод не существует?

Я что-то упустил?

Ответы [ 4 ]

51 голосов
/ 06 февраля 2009

Это раздражает, конечно. Это также часть моего проекта «MoreLINQ», на который я должен в какой-то момент обратить внимание :) Существует множество других операций, которые имеют смысл при работе с проекцией, но возвращая оригинал - MaxBy и MinBy приходят на ум. *

Как вы говорите, это легко написать - хотя я предпочитаю имя "DistinctBy", чтобы соответствовать OrderBy и т. Д. Вот моя реализация, если вам интересно:

    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;
            }
        }
    }
32 голосов
/ 06 февраля 2009

Но это кажется грязным.

Это не грязно, это правильно.

  • Если вам нужны Distinct Программисты от FirstName и есть четыре Дэвида, какой из них вам нужен?
  • Если вы Group программисты по FirstName и выберете First, тогда ясно, что вы хотите сделать в случае четырех Дэвидов.

Я могу использовать его только здесь, потому что у меня есть один ключ.

Вы можете сделать несколько «отличных» ключей с одним и тем же шаблоном:

return myList
  .GroupBy( x => new { x.Url, x.Age } )
  .Select( g => g.First() );
3 голосов
/ 08 октября 2010

Джон, ваше решение довольно хорошее. Одно небольшое изменение, хотя. Я не думаю, что нам нужен EqualityComparer.Default там. Вот мое решение (конечно, отправной точкой было решение Джона Скита)

    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;
            }
        }
    }
1 голос
/ 01 сентября 2016

Используя @ DavidB * ответ , я написал небольшой метод расширения DistinctBy, позволяющий передать предикат:

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

Теперь вы можете передать предикат для группировки списка по:

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

Или сгруппировать по нескольким свойствам:

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