LINQ:如何对集合中的所有对象的属性执行.Max(),并返回具有最大值的对象
我有一个有两个int属性的对象列表。 该列表是另一个linq查询的输出。 物体:
public class DimensionPair { public int Height { get; set; } public int Width { get; set; } }
我想find并返回具有最大Height
属性值的列表中的对象。
我可以设法获得Height
值的最高值,但不是对象本身。
我可以用Linq做这个吗? 怎么样?
我们有一个扩展方法在MoreLINQ中完成这个工作 。 你可以看看那里的实现,但基本上这是遍历数据的情况,记住了迄今为止我们所看到的最大元素以及它在投影下产生的最大值。
在你的情况下,你会做这样的事情:
var item = items.MaxBy(x => x.Height);
除Mehrdad的第二种解决scheme(基本上与MaxBy
相同)之外,这个解决scheme要比这里提出的任何解决scheme都要好(IMO):
- 这是O(n)不同于以前接受的答案 ,它在每次迭代中find最大值(使其成为O(n ^ 2))
- 订购解决scheme是O(n log n)
- 取
Max
,然后find具有该值的第一个元素是O(n),但在该序列上重复两次。 在可能的情况下,您应该使用LINQ以单通道方式。 - 阅读和理解比汇总版本要简单得多,每个元素只计算一次投影
这需要sorting(O(n log n)),但是非常简单和灵活。 另一个优势是可以在LINQ to SQL中使用它:
var maxObject = list.OrderByDescending(item => item.Height).First();
请注意,这具有枚举list
序列一次的优点。 虽然list
是一个List<T>
并不会改变,但这对任意IEnumerable<T>
对象来说可能并不重要。 没有什么能保证序列不会在不同的枚举中发生变化,所以多次执行的方法可能是危险的(而且效率低下,这取决于序列的性质)。 然而,对于大型序列来说,这仍然不是理想的解决scheme。 我build议你手动编写你自己的MaxObject
扩展,如果你有MaxObject
项目能够在一个通道内完成,而不需要sorting和其他东西(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; } }
并使用它:
var maxObject = list.MaxObject(item => item.Height);
做一个订购,然后select第一个项目是浪费了大量的时间在第一个项目之后。 你不关心这些的顺序。
相反,您可以使用聚合函数来根据您正在查找的内容来select最佳的项目。
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);
你为什么不试试这个? :
var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));
或更多优化:
var itemMaxHeight = items.Max(y => y.Height); var itemsMax = items.Where(x => x.Height == itemMaxHeight);
嗯?
迄今为止的答案是伟大的! 但是我认为需要一个解决scheme,具有以下限制:
- 简洁,简洁的LINQ;
- O(n)复杂性;
- 不要每个元素多次评估属性。
这里是:
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; }
用法:
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的列进行sorting,然后抓取第一个应该是可行的。 但是,如果有多个具有相同MAX值的对象,则只会抓取一个对象:
private void Test() { test v1 = new test(); v1.Id = 12; test v2 = new test(); v2.Id = 12; test v3 = new test(); v3.Id = 12; List<test> arr = new List<test>(); arr.Add(v1); arr.Add(v2); arr.Add(v3); test max = arr.OrderByDescending(t => t.Id).First(); } class test { public int Id { get; set; } }
在NHibernate(与NHibernate.Linq)你可以这样做,如下所示:
return session.Query<T>() .Single(a => a.Filter == filter && a.Id == session.Query<T>() .Where(a2 => a2.Filter == filter) .Max(a2 => a2.Id));
这将生成如下所示的SQL:
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')
这对我来说似乎相当有效。
根据Cameron最初的答案,这里是我刚添加的SilverFlow库的FloatingWindowHost的增强版本(从FloatingWindowHost.cs复制http://clipflair.codeplex.com源代码);
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中托pipe时使用(请参阅在Silverlight中控制渲染顺序(ZOrder)而不使用Canvas控件 )。 猜测甚至可以通过调整这个代码来轻松地为UIElement创buildSetTopmost和SetBottomMost静态扩展方法。
您也可以通过重写扩展方法来更新Mehrdad Afshari的解决scheme,以更快(更好看):
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; } }
然后只是使用它:
var maxObject = list.MaxElement(item => item.Height);
这个名字将清楚使用C ++的人(因为那里有std :: max_element)。