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

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

public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}

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

我可以设法获得Height值的最大值,但不是对象本身。

我可以用Linq做这个吗?如何?

456645 次浏览

这将需要排序(O(n 日志 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);

我相信按你想要的列排序,然后抓取第一个应该是可行的。然而,如果有多个对象具有相同的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; }
}

先排序,然后再选择第一件商品是浪费大量时间。你不关心它们的顺序。

相反,您可以使用聚合函数来根据您正在寻找的内容选择最佳项目。

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);

我们在MoreLINQ中有一个扩展方法来完成这个任务。你可以看看这里的实现,但基本上这是一个遍历数据的例子,记住我们迄今为止看到的最大元素以及它在投影下产生的最大值。

在你的情况下,你会这样做:

var item = items.MaxBy(x => x.Height);

这比这里提出的除Mehrdad的第二个解决方案(基本上与MaxBy相同)之外的任何解决方案都更好(IMO):

  • 它是O(n)不像先前接受的答案,它在每次迭代中找到最大值(使它成为O(n²))
  • 排序解是O(n log n)
  • Max值,然后找到具有该值的第一个元素是O(n),但在序列上迭代两次。在可能的情况下,应该以单遍方式使用LINQ。
  • 它比聚合版本更容易阅读和理解,并且每个元素只计算一次投影

你为什么不试试这个??:

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);

嗯?

在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是一个附加属性,可用于各种容器中的uielement,而不仅仅是在画布中托管时使用(参见在Silverlight中控制渲染顺序(ZOrder)而不使用Canvas控件)。我猜你甚至可以通过修改这段代码为UIElement创建一个SetTopmost和SetBottomMost静态扩展方法。

你也可以通过重写扩展方法来升级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)。

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

  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;
}

用法:

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