我有一个具有两个 int 属性的对象列表。该列表是另一个 linq 查询的输出。物体:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
我想在列表中查找并返回具有最大Height
属性值的对象。
我可以设法获得价值的最高值,Height
但不能获得对象本身。
我可以用 Linq 做到这一点吗?如何?
我们在MoreLINQ中有一个扩展方法可以做到这一点。您可以查看那里的实现,但基本上这是一个遍历数据的情况,记住我们迄今为止看到的最大元素以及它在投影下产生的最大值。
在您的情况下,您会执行以下操作:
var item = items.MaxBy(x => x.Height);
这比 Mehrdad 的第二个解决方案(与 基本相同)之外的任何解决方案都更好(IMO MaxBy
):
Max
,然后找到具有该值的第一个元素是 O(n),但迭代序列两次。在可能的情况下,您应该以单遍方式使用 LINQ。这将需要一个排序(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);
进行订购然后选择第一个项目会浪费大量时间在第一个项目之后订购项目。你不关心这些的顺序。
相反,您可以使用聚合函数根据您要查找的内容选择最佳项目。
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);
嗯?
到目前为止的答案都很棒!但我认为需要具有以下限制的解决方案:
这里是:
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 的列进行排序,然后抓住第一个应该可行。但是,如果有多个具有相同 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 增强版中添加的内容(从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 中控制渲染顺序 (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 )。