【问题标题】:Why is there no Linq method to return distinct values by a predicate?为什么没有 Linq 方法通过谓词返回不同的值?
【发布时间】:2009-02-06 11:51:53
【问题描述】:

我想获取列表中的不同值,但不是通过标准相等比较。

我想做的是这样的:

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
}

但这似乎更像是在写一些本来就应该存在的东西。

有人知道为什么没有这种方法吗?

我错过了什么吗?

【问题讨论】:

    标签: c# linq distinct


    【解决方案1】:

    当然,这很烦人。这也是我的“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;
                }
            }
        }
    

    【讨论】:

    • 感谢您的快速回答 - 我可能会使用它!知道他们为什么跳过所有这些 ...By(Predicate) 方法吗?
    • 恐怕不是。当我拥有一组重要的功能时,我将在博客上介绍 MoreLinq 项目......基本上它将是一个开源项目,扩展了 LINQ to Objects,并且可能还会推送 LINQ。
    • 如果我不得不猜测,我会猜测与 IQueryable 选项的奇偶性,以及 TSQL 中什么是现实的(不会生病)。所以 DISTINCT(table.column) 很好,但是你需要一个方便的键和一些更复杂的 TSQL 用于 DistinctBy...
    • 马克说得很好——如果你把它作为答案发布,我会投赞成票的。
    【解决方案2】:

    但这看起来很乱。

    不是乱七八糟,是正确的。

    • 如果您想要Distinct Programmers by FirstName,并且有四个 Amy,您想要哪一个?
    • 如果你Groupprogrammers By FirstName 并拿First 一个,那么在四个 Amy 的情况下你想做什么就很清楚了。

    我只能在这里使用它,因为我只有一个键。

    你可以用相同的模式做一个多键“不同”:

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

    【讨论】:

      【解决方案3】:

      乔恩,您的解决方案非常好。一个小小的变化。我认为我们不需要 EqualityComparer.Default 。这是我的解决方案(当然起点是 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;
                  }
              }
          }
      

      【讨论】:

      • 我不确定为什么这会比 Jon 的解决方案更好。 new HashSet&lt;TKey&gt;() 无论如何都会使用 EqualityComparer&lt;TKey&gt;.Default 并且按照你的方式这样做你将失去覆盖它的能力(例如,如果 TKeystring 并且你想要不区分大小写)。 Jon 也使用HashSet.Add 方法,而您使用HashSet.Contains 然后HashSet.Add - 两个操作。诚然,您需要一个庞大的集合才能注意到差异,但为什么要让它变慢呢?
      【解决方案4】:

      使用 AmyB 的 answer,我编写了一个小的 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 });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-07
        相关资源
        最近更新 更多