我有一个具有两个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做这个吗?如何?
当前回答
先排序,然后再选择第一件商品是浪费大量时间。你不关心它们的顺序。
相反,您可以使用聚合函数来根据您正在寻找的内容选择最佳项目。
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);
我相信按你想要的列排序,然后抓取第一个应该是可行的。然而,如果有多个对象具有相同的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; }
}
到目前为止的答案都很棒!但我认为需要一种具有以下约束的解决方案:
朴素、简洁的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
先排序,然后再选择第一件商品是浪费大量时间。你不关心它们的顺序。
相反,您可以使用聚合函数来根据您正在寻找的内容选择最佳项目。
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。 它比聚合版本更容易阅读和理解,并且每个元素只计算一次投影