如何对集合中所有对象的属性执行.Max()并返回具有最大值的对象[重复]
Posted
技术标签:
【中文标题】如何对集合中所有对象的属性执行.Max()并返回具有最大值的对象[重复]【英文标题】:How to perform .Max() on a property of all objects in a collection and return the object with maximum value [duplicate] 【发布时间】:2010-11-09 06:23:17 【问题描述】:我有一个具有两个 int 属性的对象列表。该列表是另一个 linq 查询的输出。对象:
public class DimensionPair
public int Height get; set;
public int Width get; set;
我想在列表中查找并返回具有最大Height
属性值的对象。
我可以设法获得 Height
值的最大值,但不能获得对象本身。
我可以用 Linq 做到这一点吗?怎么样?
【问题讨论】:
var maxDimension = dimensions.OrderByDesc(x=>x.Height).FirstOrDefault(); 多么简单实用的功能啊。 MaxBy 函数应该在标准库中。我们应该向 Microsoft 提出功能请求github.com/dotnet/corefx 【参考方案1】:我们有一个extension method 在MoreLINQ 中执行此操作。你可以看看那里的实现,但基本上这是一个遍历数据的例子,记住我们到目前为止看到的最大元素以及它在投影下产生的最大值。
在你的情况下,你会做这样的事情:
var item = items.MaxBy(x => x.Height);
除了 Mehrdad 的第二个解决方案(与 MaxBy
基本相同)之外,这比此处介绍的任何解决方案都更好 (IMO):
Max
值,然后找到具有该值的第一个元素是 O(n),但会迭代序列两次。在可能的情况下,您应该以单遍方式使用 LINQ。
它比聚合版本更易于阅读和理解,并且每个元素只评估一次投影
【讨论】:
Linq 扩展已经有一个Max
方法,您也可以将它与 lambda 表达式 var item = items.Max(x => x.Height);
一起使用
@sgissinger:你错过了重点 - 这将给出集合中的最大高度,而不是 最大高度的项目。
FWIW 这是聚合版本:items.Aggregate((i, j) => i.Height > j.Height ? i : j)
@orad:是的,可以做到——但表达力要差得多,IMO。
MaxBy/MinBy 完美地完成了这项工作。 +1 为 MoreLinq 的礼物!谢谢@JonSkeet(顺便通过 NuGet 添加了 MoreLinq)【参考方案2】:
这需要排序 (O(n log n)),但非常简单和灵活。另一个优点是能够将它与 LINQ to SQL 一起使用:
var maxObject = list.OrderByDescending(item => item.Height).First();
请注意,这样做的好处是只枚举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;
并将其用于:
var maxObject = list.MaxObject(item => item.Height);
【讨论】:
老鼠,在我发帖之前没看到这个。这基本上就是我们在 MoreLINQ 中所做的,只是我们直接使用 GetEnumerator 进行迭代,而不是使用“first”标志。我认为它使代码更简单一些。我们还允许使用 Comparer 并允许传入一个,而不是要求 U 实现 IComparable,但这是一个附带问题。 最后一部分是一个简单问题的很多不必要的复杂性。 这是一个更通用问题的解决方案。基本上,您声明这样一个扩展方法是为了在需要时抽象复杂性。 软件开发不就是这样吗? 为了完整起见,有什么理由更喜欢OrderByDescending().First()
或OrderBy().Last()
?我在我的代码中使用了第二个选项(至少更短),因为我认为两者的成本相同? (编辑:好吧,没关系...***.com/questions/6680666/…)【参考方案3】:
进行订购然后选择第一个项目会浪费大量时间订购第一个项目之后的项目。你不关心这些的顺序。
相反,您可以使用聚合函数根据您要查找的内容选择最佳项目。
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);
【讨论】:
那么这是 O(n) 吗?它是如何处理空列表的? 这应该是公认的答案。任何其他方法都将迭代对象超过 1 次或使用不需要的库。另见:***.com/questions/3188693/… maxHeightAndWidth 将根据列表的顺序执行不一致。例如,如果您只有 1, 3 和 3, 1,则无论顺序如何,它都会返回列表中的第一项。所以它实际上并没有用。 @meustrus 对于 maxHeightAndWidth 1, 3 和 3, 1 是等效的,因为值没有顺序(高度在宽度之前,或宽度在高度之前)。由于它们是等效的,因此首先返回的应该没有区别。 @ChristopheDeTroyer 如果不迭代整个列表,就不可能找到最大值。【参考方案4】:你为什么不试试这个??? :
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);
嗯?
【讨论】:
因为这是 O(n^2)。对于列表中的每个项目,它会遍历所有项目并找到高度最大的项目,然后检查当前项目是否相同高度,并将其返回。适合小型列表,但超过 100 项您会发现差异。 你是对的,所以你必须在从 lambda 表达式中提取 items.Max(y => y.Height) 并将其保存在变量中之前。 var itemMaxHeight = items.Max(y => y.Height); var item = items.Where(x => x.Height == itemMaxHeight);我写得很快,因为我认为这可能是一种更简单的方法。但要优化最好是这样做,这是真的。 现在更优化的版本是 O(2n) 更好,但现在需要一个临时变量和 2 行,并且仍然不像其他一些答案那样 O(n)。 耸耸肩 你确定 items.Max(y => y.Height) 的结果不会被缓存吗?我还认为(就大 O 表示法而言)O(2n) 与 O(n) 相同? @CameronMacFarland 我想指出-数学上讲-O(n)= O(2n),在复杂性中重要的因素是指数和幂。 (我可能以不精确的方式表达这一点,因为我有一段时间研究过这个,如果有人能更好地表达它,请随时编辑我的帖子)【参考方案5】:到目前为止的答案都很棒!但我认为需要具有以下限制的解决方案:
-
简单、简洁的 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
【讨论】:
【参考方案6】:我相信按您想要获得 MAX 的列进行排序然后抓取第一个应该可行。但是,如果有多个具有相同 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;
【讨论】:
【参考方案7】:在 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')
这对我来说似乎很有效。
【讨论】:
【参考方案8】:根据 Cameron 的初步回答,这是我刚刚在 SilverFlow 库的 FloatingWindowHost 增强版中添加的内容(从 http://clipflair.codeplex.com 源代码的 FloatingWindowHost.cs 复制)
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 是一个附加属性,可用于各种容器中的 UIElement,而不仅仅是在 Canvas 中托管时使用(请参阅Controlling rendering order (ZOrder) in Silverlight without using the Canvas control)。猜猜我们甚至可以通过修改此代码轻松地为 UIElement 制作 SetTopmost 和 SetBottomMost 静态扩展方法。
【讨论】:
哎呀,抱歉,没有看到您想要返回对象,而不是值 - 无论如何,如果有人想知道如何像我一样返回最大值【参考方案9】:您还可以通过将扩展方法重写为更快(更好看)的方法来升级 Mehrdad Afshari 的解决方案:
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)。
【讨论】:
以上是关于如何对集合中所有对象的属性执行.Max()并返回具有最大值的对象[重复]的主要内容,如果未能解决你的问题,请参考以下文章