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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1101841/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 08:16:05  来源:igfitidea点击:

How to perform .Max() on a property of all objects in a collection and return the object with maximum value

c#linqobjectmax

提问by theringostarrs

I have a list of objects that have two int properties. The list is the output of another linq query. The object:

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

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

I want to find and return the object in the list which has the largest Heightproperty value.

我想在列表中查找并返回具有最大Height属性值的对象。

I can manage to get the highest value of the Heightvalue but not the object itself.

我可以设法获得价值的最高价值,Height但不能获得对象本身。

Can I do this with Linq? How?

我可以用 Linq 做到这一点吗?如何?

采纳答案by Jon Skeet

We have an extension methodto do exactly this in MoreLINQ. You can look at the implementation there, but basically it's a case of iterating through the data, remembering the maximum element we've seen so far and the maximum value it produced under the projection.

我们有一个扩展方法可以在MoreLINQ 中做到这一点。您可以查看那里的实现,但基本上这是一个遍历数据的情况,记住我们迄今为止看到的最大元素以及它在投影下产生的最大值。

In your case you'd do something like:

在您的情况下,您会执行以下操作:

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

This is better (IMO) than any of the solutions presented here other than Mehrdad's second solution (which is basically the same as MaxBy):

除了 Mehrdad 的第二个解决方案(基本上与 相同)之外,这比此处介绍的任何解决方案都更好(IMO MaxBy):

  • It's O(n) unlike the previous accepted answerwhich finds the maximum value on every iteration (making it O(n^2))
  • The ordering solution is O(n log n)
  • Taking the Maxvalue and then finding the first element with that value is O(n), but iterates over the sequence twice. Where possible, you should use LINQ in a single-pass fashion.
  • It's a lot simpler to read and understand than the aggregate version, and only evaluates the projection once per element
  • 它是 O(n) 与之前接受的答案不同,它在每次迭代中找到最大值(使其成为 O(n^2))
  • 排序解决方案是 O(n log n)
  • Max的值,然后找出与该值的第一个元素是O(n),但在序列迭代两次。在可能的情况下,您应该以单程方式使用 LINQ。
  • 它比聚合版本更易于阅读和理解,并且每个元素只计算一次投影

回答by Mehrdad Afshari

This would require a sort (O(n logn)) but is very simple and flexible. Another advantage is being able to use it with LINQ to SQL:

这将需要排序 (O(n logn)) 但非常简单和灵活。另一个优点是能够将它与 LINQ to SQL 一起使用:

var maxObject = list.OrderByDescending(item => item.Height).First();

Note that this has the advantage of enumerating the listsequence just once. While it might not matter if listis 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 MaxObjectextension 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)):

请注意,这具有list仅枚举序列一次的优点。虽然在此期间不改变的listaList<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;
    }
}

and use it with:

并将其用于:

var maxObject = list.MaxObject(item => item.Height);

回答by regex

I believe that sorting by the column you want to get the MAX of and then grabbing the first should work. However, if there are multiple objects with the same MAX value, only one will be grabbed:

我相信按您想要获得 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; }
}

回答by Cameron MacFarland

Doing an ordering and then selecting the first item is wasting a lot of time ordering the items after the first one. You don't care about the order of those.

订购然后选择第一个项目会浪费大量时间在第一个项目之后订购项目。你不在乎这些的顺序。

Instead you can use the aggregate function to select the best item based on what you're looking for.

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

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

回答by Dragouf

And why don't you try with this ??? :

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

var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));

OR more optimise :

或更多优化:

var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);

mmm ?

嗯?

回答by Tod Thomson

In NHibernate (with NHibernate.Linq) you could do it as follows:

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

Which will generate SQL like follows:

这将生成如下 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')

Which seems pretty efficient to me.

这对我来说似乎非常有效。

回答by George Birbilis

Based on Cameron's initial answer, here is what I've just added at my enhanced version of SilverFlow library's FloatingWindowHost (copying from FloatingWindowHost.cs at http://clipflair.codeplex.comsource code)

根据 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);
    }

Worth noting regarding the code above that Canvas.ZIndex is an attached property available for UIElements in various containers, not just used when being hosted in a Canvas (see Controlling rendering order (ZOrder) in Silverlight without using the Canvas control). Guess one could even make a SetTopmost and SetBottomMost static extension method for UIElement easily by adapting this code.

值得注意的是,关于上面的代码,Canvas.ZIndex 是可用于各种容器中 UIElements 的附加属性,而不仅仅是在 Canvas 中托管时使用(请参阅在不使用 Canvas 控件的情况下控制 Silverlight 中的渲染顺序 (ZOrder))。猜猜通过修改此代码,甚至可以轻松地为 UIElement 创建 SetTopmost 和 SetBottomMost 静态扩展方法。

回答by Maciej Ozi?b?y

You can also upgrade Mehrdad Afshari's solution by rewriting the extention method to faster (and better looking) one:

您还可以通过将扩展方法重写为更快(更好看)的方法来升级 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;
    }
}

And then just use it:

然后只需使用它:

var maxObject = list.MaxElement(item => item.Height);

That name will be clear to people using C++ (because there is std::max_element in there).

这个名字对于使用 C++ 的人来说很清楚(因为那里有 std::max_element )。

回答by meustrus

The answers so far are great! But I see a need for a solution with the following constraints:

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

  1. Plain, concise LINQ;
  2. O(n) complexity;
  3. Do not evaluate the property more than once per element.
  1. 简洁、简洁的 LINQ;
  2. O(n) 复杂度;
  3. 不要对每个元素多次评估该属性。

Here it is:

这里是:

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

Usage:

用法:

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