LINQ:如何对集合中所有对象的属性执行.Max()并返回具有最大值的对象

the*_*rrs 264 c# linq object max

我有一个具有两个int属性的对象列表.该列表是另一个linq查询的输出.物体:

public class DimensionPair  
{
    public int Height { get; set; }
    public int Width { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我想找到并返回列表中具有最大Height属性值的对象.

我可以设法获得值的最高值,Height但不能获得对象本身.

我可以用Linq做到这一点吗?怎么样?

Jon*_*eet 265

我们有一个扩展方法,可以在MoreLINQ中完成此操作.您可以查看那里的实现,但基本上是迭代数据的情况,记住我们到目前为止看到的最大元素以及它在投影下产生的最大值.

在你的情况下你会做类似的事情:

var item = items.MaxBy(x => x.Height);
Run Code Online (Sandbox Code Playgroud)

除了Mehrdad的第二个解决方案(基本相同)之外,这比其他任何解决方案更好(IMO MaxBy):

  • 它是O(n)不同于先前接受的答案,它在每次迭代中找到最大值(使其为O(n ^ 2))
  • 订购解决方案是O(n log n)
  • Max的值,然后找出与该值的第一个元素是O(n),但在序列迭代两次.在可能的情况下,您应该以单通方式使用LINQ.
  • 阅读和理解比聚合版本简单得多,并且每个元素只评估一次投影

  • @sgissinger:你错过了这一点 - 这将给出集合中的最大高度,而不是具有*最大高度的项目*. (81认同)
  • Linq扩展已经有一个[`Max`](http://msdn.microsoft.com/en-us/library/Bb343284(v = vs.90).aspx)方法,你也可以使用lambda表达式`var item = items.Max(x => x.Height);` (33认同)
  • FWIW这是聚合版本:`items.Aggregate((i,j)=> i.Height> j .Height?i:j)` (16认同)
  • @orad:是的,那样做 - 但表达力要低得多,IMO. (3认同)
  • MaxBy/MinBy 完美地完成了这项工作。+1 为 MoreLinq 的礼物!感谢 @JonSkeet(顺便通过 NuGet 添加了 MoreLinq) (3认同)
  • @jam40jeff:好吧,“MaxBy”*准确地*说了您想要它做什么。基本上不需要翻译。您是否更愿意使用“Aggregate”而不是“Count()”?这同样可行,但在我看来可读性较差...... (2认同)
  • @liang:从外观上看,它随着时间的推移而改变。当然,当我编写第一个版本时,它返回了一个元素。看起来它在 2018 年 3 月发生了变化。(我没有时间回去找到我在 Stack Overflow 上提到 MaxBy 的所有答案。看起来如果你使用 2.x 版本,你仍然会得到单元素版本。) (2认同)

Meh*_*ari 195

这需要排序(O(n log n)),但非常简单和灵活.另一个优点是能够将它与LINQ to SQL一起使用:

var maxObject = list.OrderByDescending(item => item.Height).First();
Run Code Online (Sandbox Code Playgroud)

请注意,这具有list仅枚举序列一次的优点.虽然在此期间如果不改变可能无关紧要,但它对任意对象list都很List<T>重要IEnumerable<T>.没有什么能保证序列在不同的枚举中不会改变,因此多次执行它的方法可能是危险的(并且效率低,取决于序列的性质).然而,对于大型序列来说,它仍然不是理想的解决方案.我建议MaxObject手动编写自己的扩展,如果你有一大堆项目可以在一次通过中完成,而无需排序和其他任何东西(O(n)):

static class EnumerableExtensions {
    public static T MaxObject<T,U>(this IEnumerable<T> source, Func<T,U> selector)
      where U : IComparable<U> {
       if (source == null) throw new ArgumentNullException("source");
       bool first = true;
       T maxObj = default(T);
       U maxKey = default(U);
       foreach (var item in source) {
           if (first) {
                maxObj = item;
                maxKey = selector(maxObj);
                first = false;
           } else {
                U currentKey = selector(item);
                if (currentKey.CompareTo(maxKey) > 0) {
                    maxKey = currentKey;
                    maxObj = item;
                }
           }
       }
       if (first) throw new InvalidOperationException("Sequence is empty.");
       return maxObj;
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

var maxObject = list.MaxObject(item => item.Height);
Run Code Online (Sandbox Code Playgroud)

  • 这是一个更通用的问题的解决方案.基本上,您可以声明这样的扩展方法,以便在需要时抽象出*复杂性. (5认同)
  • 这不是软件开发的工作原理吗? (5认同)
  • 老鼠,在我发布之前没看到这个.这基本上就是我们在MoreLINQ中所做的,除了我们直接使用GetEnumerator进行迭代而不是使用"first"标志.我认为它使代码更简单.我们还允许使用Comparer <U>并允许传入,而不是要求U实现IComparable,但这是一个副作用. (2认同)

Cam*_*and 137

进行订购然后选择第一项是浪费了大量时间在第一项之后订购商品.你不关心那些的顺序.

相反,您可以使用聚合函数根据您要查找的内容选择最佳项目.

var maxHeight = dimensions
    .Aggregate((agg, next) => 
        next.Height > agg.Height ? next : agg);

var maxHeightAndWidth = dimensions
    .Aggregate((agg, next) => 
        next.Height >= agg.Height && next.Width >= agg.Width ? next: agg);
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案.任何其他方法将迭代对象超过1次或使用不需要的库.另见:http://stackoverflow.com/questions/3188693/how-can-i-get-linq-to-return-the-object-which-has-the-max-value-for-a-given-prop/3188804#3188804 (10认同)
  • 那么这是 O(n) 吗?它如何处理空列表? (2认同)
  • @ChristopheDeTroyer 如果不迭代整个列表就不可能找到最大值。 (2认同)

Dra*_*ouf 33

你为什么不尝试这个?:

var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));
Run Code Online (Sandbox Code Playgroud)

或者更优化:

var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);
Run Code Online (Sandbox Code Playgroud)

嗯?

  • 因为这是O(n ^ 2).对于列表中的每个项目,它遍历所有项目并找到具有最大高度的项目,然后检查当前项目是否具有相同的高度,并返回它.适用于小型列表,但超过100个项目您会注意到不同之处. (13认同)
  • 你确定items.Max(y => y.Height)的结果不会被缓存吗?我还想(就大O符号而言)O(2n)与O(n)相同? (3认同)
  • 你是对的,所以你必须在从lambda表达式中提取items.Max(y => y.Height)并将其保存在变量中之前.var itemMaxHeight = items.Max(y => y.Height); var item = items.Where(x => x.Height == itemMaxHeight); 我写得很快,因为我认为这可能是一种更简单的方法.但为了优化它,最好这样做,这是真的. (2认同)
  • @CameronMacFarland 我想指出 - 从数学上讲 - O(n) = O(2n),复杂性中重要的因素是指数和幂。(我可能会以不准确的方式表达这一点,因为我研究了一段时间,如果有人能更好地表达它,请随时编辑我的帖子) (2认同)

meu*_*rus 21

到目前为止答案很棒!但我认为需要一个具有以下约束的解决方案:

  1. 简洁明了的LINQ;
  2. O(n)复杂性;
  3. 不要每个元素多次评估该属性.

这里是:

public static T MaxBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
    return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
        .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1;
}

public static T MinBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
    return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
        .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1;
}
Run Code Online (Sandbox Code Playgroud)

用法:

IEnumerable<Tuple<string, int>> list = new[] {
    new Tuple<string, int>("other", 2),
    new Tuple<string, int>("max", 4),
    new Tuple<string, int>("min", 1),
    new Tuple<string, int>("other", 3),
};
Tuple<string, int> min = list.MinBy(x => x.Item2); // "min", 1
Tuple<string, int> max = list.MaxBy(x => x.Item2); // "max", 4
Run Code Online (Sandbox Code Playgroud)