How to perform .Max() on a property of all objects in a collection and return the object with maximum value
我有一个具有两个int属性的对象列表。 该列表是另一个linq查询的输出。 物体:
1 2 3 4 5 | public class DimensionPair { public int Height { get; set; } public int Width { get; set; } } |
我想查找并返回列表中具有最大
我可以设法获得
我可以用Linq做到这一点吗? 怎么样?
我们有一个扩展方法,可以在MoreLINQ中完成此操作。您可以查看那里的实现,但基本上是迭代数据的情况,记住我们到目前为止看到的最大元素以及它在投影下产生的最大值。
在你的情况下你会做类似的事情:
1 | var item = items.MaxBy(x => x.Height); |
除了Mehrdad的第二个解决方案(基本上与
- 它是O(n)不同于先前接受的答案,它在每次迭代中找到最大值(使其为O(n ^ 2))
- 订购解决方案是O(n log n)
-
取
Max 值,然后找到具有该值的第一个元素是O(n),但迭代序列两次。在可能的情况下,您应该以单通方式使用LINQ。 - 阅读和理解比聚合版本简单得多,并且每个元素只评估一次投影
这需要排序(O(n log n)),但非常简单和灵活。另一个优点是能够将它与LINQ to SQL一起使用:
1 | var maxObject = list.OrderByDescending(item => item.Height).First(); |
请注意,这具有仅枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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; } } |
并使用它:
1 | var maxObject = list.MaxObject(item => item.Height); |
进行订购然后选择第一项是浪费了大量时间在第一项之后订购商品。你不关心那些的顺序。
相反,您可以使用聚合函数根据您要查找的内容选择最佳项目。
1 2 3 4 5 6 7 | 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); |
你为什么不尝试这个? :
1 | var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height)); |
或者更优化:
1 2 | var itemMaxHeight = items.Max(y => y.Height); var itemsMax = items.Where(x => x.Height == itemMaxHeight); |
嗯?
到目前为止答案很棒!但我认为需要一个具有以下约束的解决方案:
这里是:
1 2 3 4 5 6 7 8 9 | 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; } |
用法:
1 2 3 4 5 6 7 8 | 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 |
我相信按照你想要获得MAX的列进行排序然后抓住第一个应该可行。但是,如果有多个具有相同MAX值的对象,则只会抓取一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
在NHibernate(使用NHibernate.Linq)中,您可以按如下方式执行此操作:
1 2 3 4 5 | return session.Query< T >() .Single(a => a.Filter == filter && a.Id == session.Query< T >() .Where(a2 => a2.Filter == filter) .Max(a2 => a2.Id)); |
这将产生如下SQL:
1 2 3 4 5 6 | select * from TableName foo where foo.Filter = 'Filter On String' and foo.Id = (select cast(max(bar.RowVersion) as INT) from TableName bar where bar.Name = 'Filter On String') |
这对我来说似乎非常有效。
您还可以通过将扩展方法重写为更快(更好看)的方法来升级Mehrdad Afshari的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | static class EnumerableExtensions { public static T MaxElement<T, R>(this IEnumerable< T > container, Func<T, R> valuingFoo) where R : IComparable { var enumerator = container.GetEnumerator(); if (!enumerator.MoveNext()) throw new ArgumentException("Container is empty!"); var maxElem = enumerator.Current; var maxVal = valuingFoo(maxElem); while (enumerator.MoveNext()) { var currVal = valuingFoo(enumerator.Current); if (currVal.CompareTo(maxVal) > 0) { maxVal = currVal; maxElem = enumerator.Current; } } return maxElem; } } |
然后使用它:
1 | var maxObject = list.MaxElement(item => item.Height); |
使用C ++的人会清楚这个名称(因为那里有std :: max_element)。
根据Cameron的初步答案,以下是我刚刚在我的SilverFlow库的FloatingWindowHost增强版中添加的内容(从http://clipflair.codeplex.com源代码中的FloatingWindowHost.cs复制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public int MaxZIndex { get { return FloatingWindows.Aggregate(-1, (maxZIndex, window) => { int w = Canvas.GetZIndex(window); return (w > maxZIndex) ? w : maxZIndex; }); } } private void SetTopmost(UIElement element) { if (element == null) throw new ArgumentNullException("element"); Canvas.SetZIndex(element, MaxZIndex + 1); } |
值得注意的是,上面的代码Canvas.ZIndex是一个附加属性,可用于各种容器中的UIElements,而不仅仅是在Canvas中托管时使用(请参阅在Silverlight中控制渲染顺序(ZOrder)而不使用Canvas控件)。猜测一个人甚至可以通过调整这个代码轻松地为UIElement制作一个SetTopmost和SetBottomMost静态扩展方法。