【问题标题】:What is faster in finding element with property of maximum value找到具有最大值属性的元素更快
【发布时间】:2016-05-29 10:46:27
【问题描述】:

通常,找到具有最大值属性的元素我喜欢这样

var itemWithMaxPropValue = collection.OrderByDescending(x => x.Property).First();

但从性能的角度来看,这是一种好方法吗?也许我应该这样做?

var maxValOfProperty = collection.Max(x => x.Property);
var itemWithMaxPropValue = collection
                                 .Where(x => x.Property == maxValueOfProperty).First();

【问题讨论】:

  • 我会选择Max,因为它是专门为它设计的...排序以找到最大值似乎太多了...另外,我不会使用Where 来查找最大值,但 Single
  • OrderByDescending / 快速排序应该是 O(n^2) 和 Max 是 O(n) 和 Where O(n) - 所以第二个方法应该更快
  • 请参阅nuget.org/packages/MoreLinq.Source.MoreEnumerable.MaxBy 以了解内存使用(不会转换为 sql)

标签: c# performance linq collections


【解决方案1】:

排序是 N * log (N),而 Max 的 N 只有时间复杂度,所以 Max 更快。您正在寻找的是 Linq 不提供的ArgMax 函数,所以我建议实现它,例如:

  public static class EnumerableExtensions {
    public static T ArgMax<T, K>(this IEnumerable<T> source, 
                                 Func<T, K> map, 
                                 IComparer<K> comparer = null) {
      if (Object.ReferenceEquals(null, source))
        throw new ArgumentNullException("source");
      else if (Object.ReferenceEquals(null, map))
        throw new ArgumentNullException("map");

      T result = default(T);
      K maxKey = default(K);
      Boolean first = true;

      if (null == comparer)
        comparer = Comparer<K>.Default;

      foreach (var item in source) {
        K key = map(item);

        if (first || comparer.Compare(key, maxKey) > 0) {
          first = false;
          maxKey = key;
          result = item;
        }
      }

      if (!first)
        return result;
      else
        throw new ArgumentException("Can't compute ArgMax on empty sequence.", "source");
    }
  }

所以你可以简单地说

  var itemWithMaxPropValue = collection
    .ArgMax(x => x.Property);

【讨论】:

    【解决方案2】:

    我会选择Max,因为它是专门为此目的而设计的。排序查找Max 值似乎太多了。

    另外,我不会使用Where 来查找最大值,而是使用Single - 因为我们在这里需要的只是一个Single 值。

    var maxValOfProperty = collection.Max(x => x.Property);
    var itemWithMaxPropValue = collection
                                .Single(x => x.Property == maxValueOfProperty);
    

    或者使用First(如果集合包含最大值的重复项)

    var maxValOfProperty = collection.Max(x => x.Property);
    var itemWithMaxPropValue = collection
                                .First(x => x.Property == maxValueOfProperty);
    

    或者,使用MoreLINQ(如Kathi 建议的那样),您可以使用MaxBy

    var itemWithMaxPropValue = collection.MaxBy(x => x.Property);
    

    检查此post,由Jon Skeet 回答。

    【讨论】:

    • 如果集合包含多个项目(具有属性的最大值)并且 OP 不关心取哪个项目,您可能希望使用 First 而不是 Single在这种情况下。
    • @YacoubMassad 你是对的,这当然是一个有效的选择。
    • 另请注意,如果将这两行合并,collection.Max(x =&gt; x.Property) 可能会被计算多次。
    • @Kathi 你的意思是:msdn.microsoft.com/en-us/library/… ?看起来它不是典型的 LINQ,我不确定它的性能......
    • @Ian 如果那是 MoreLINQ 那么这就是我的意思,但也请检查一下:stackoverflow.com/a/1101931/5210934,你会在答案中找到MaxBy(..) 的东西
    【解决方案3】:

    这两种解决方案都不是很有效。第一个解决方案涉及对整个集合进行排序。第二种解决方案需要遍历集合两次。但是您可以一次性找到具有最大属性值的项目,而无需对集合进行排序。 MoreLINQ 库中有 MaxBy 扩展。或者您可以实现相同的功能:

    public static TSource MaxBy<TSource, TProperty>(this IEnumerable<TSource> source,
        Func<TSource, TProperty> selector)
    {
        // check args        
    
        using (var iterator = source.GetEnumerator())
        {
            if (!iterator.MoveNext())            
                throw new InvalidOperationException();
    
            var max = iterator.Current; 
            var maxValue = selector(max);
            var comparer = Comparer<TProperty>.Default;
    
            while (iterator.MoveNext())
            {
                var current = iterator.Current;
                var currentValue = selector(current);
    
                if (comparer.Compare(currentValue, maxValue) > 0)
                {
                    max = current;
                    maxValue = currentValue;
                }
            }
    
            return max;
        }
    }
    

    用法很简单:

    var itemWithMaxPropValue = collection.MaxBy(x => x.Property); 
    

    【讨论】:

      【解决方案4】:

      某个指定函数下的最大元素也可以通过以下两个函数求出。

      static class Tools
      {
          public static T ArgMax<T, R>(T t1, T t2, Func<T, R> f)
          where R : IComparable<R>
          {
              return f(t1).CompareTo(f(t2)) > 0 ? t1 : t2;
          }
      
          public static T ArgMax<T, R>(this IEnumerable<T> Seq, Func<T, R> f)
          where R : IComparable<R>
          {
              return Seq.Aggregate((t1, t2) => ArgMax<T, R>(t1, t2, f));
          }
      }
      

      上述解决方案的工作原理如下; ArgMax 的第一个重载将比较器作为参数,将 T 的两个实例映射到实现可比性的类型;最多返回这些。第二个重载将序列作为参数并简单地聚合第一个函数。这是我所知道的用于最大搜索的最通用、框架重用和结构合理的公式;通过更改第一个函数中的比较,可以以相同的方式实现搜索最小值。

      【讨论】:

        猜你喜欢
        • 2013-06-27
        • 1970-01-01
        • 1970-01-01
        • 2013-11-14
        • 1970-01-01
        • 2014-06-09
        • 2015-08-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多