我有一个具有两个int属性的对象列表。该列表是另一个linq查询的输出。对象:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
我想在列表中找到并返回具有最大Height属性值的对象。
我可以设法获得高度值的最大值,但不是对象本身。
我可以用Linq做这个吗?如何?
我有一个具有两个int属性的对象列表。该列表是另一个linq查询的输出。对象:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
我想在列表中找到并返回具有最大Height属性值的对象。
我可以设法获得高度值的最大值,但不是对象本身。
我可以用Linq做这个吗?如何?
当前回答
根据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中托管时使用(参见在Silverlight中不使用Canvas控件控制渲染顺序(ZOrder))。我猜你甚至可以通过修改这段代码为UIElement创建一个SetTopmost和SetBottomMost静态扩展方法。
其他回答
到目前为止的答案都很棒!但我认为需要一种具有以下约束的解决方案:
朴素、简洁的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
根据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中托管时使用(参见在Silverlight中不使用Canvas控件控制渲染顺序(ZOrder))。我猜你甚至可以通过修改这段代码为UIElement创建一个SetTopmost和SetBottomMost静态扩展方法。
我们在MoreLINQ中有一个扩展方法可以做到这一点。你可以看看这里的实现,但基本上这是一个遍历数据的例子,记住我们迄今为止看到的最大元素以及它在投影下产生的最大值。
在你的情况下,你会这样做:
var item = items.MaxBy(x => x.Height);
这比这里提出的除Mehrdad的第二个解决方案(基本上与MaxBy相同)之外的任何解决方案都更好(IMO):
它是O(n)不像之前接受的答案,它在每次迭代中找到最大值(使它成为O(n²)) 排序解是O(n log n) 取Max值,然后找到具有该值的第一个元素是O(n),但在序列上迭代两次。在可能的情况下,应该以单遍方式使用LINQ。 它比聚合版本更容易阅读和理解,并且每个元素只计算一次投影
先排序,然后再选择第一件商品是浪费大量时间。你不关心它们的顺序。
相反,您可以使用聚合函数来根据您正在寻找的内容选择最佳项目。
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 log n)),但非常简单和灵活。另一个优点是可以与LINQ to SQL一起使用:
var maxObject = list.OrderByDescending(item => item.Height).First();
Note that this has the advantage of enumerating the list sequence just once. While it might not matter if list is a List<T> that doesn't change in the meantime, it could matter for arbitrary IEnumerable<T> objects. Nothing guarantees that the sequence doesn't change in different enumerations so methods that are doing it multiple times can be dangerous (and inefficient, depending on the nature of the sequence). However, it's still a less than ideal solution for large sequences. I suggest writing your own MaxObject extension manually if you have a large set of items to be able to do it in one pass without sorting and other stuff whatsoever (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);