LINQ:如何在集合中的所有对象的属性上执行.Max(),并返回具有最大值的对象[复制]

时间:2022-10-02 20:29:09

This question already has an answer here:

这个问题已经有了答案:

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 Height property value.

我希望找到并返回具有最大高度属性值的列表中的对象。

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

我可以设法得到高度值的最高值,而不是对象本身。

Can I do this with Linq? How?

我能用Linq做这个吗?如何?

9 个解决方案

#1


201  

We have an extension method to 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的第二个解决方案(基本上与MaxBy相同)更好(IMO):

  • It's O(n) unlike the previous accepted answer which finds the maximum value on every iteration (making it O(n^2))
  • 这是O(n)与先前接受答案找到最大值在每个迭代(使它O(n ^ 2))
  • The ordering solution is O(n log n)
  • 顺序解是O(n log n)
  • Taking the Max value 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.
  • 取最大值,然后找到具有该值的第一个元素是O(n),但是遍历序列两次。在可能的情况下,您应该以单通道方式使用LINQ。
  • It's a lot simpler to read and understand than the aggregate version, and only evaluates the projection once per element
  • 与聚合版本相比,阅读和理解要简单得多,并且只计算每个元素的投影。

#2


152  

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

这需要排序(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)):

请注意,这具有枚举列表序列的优势。虽然list是list ,但在此期间没有变化,但它可能对任意IEnumerable 对象很重要。没有任何东西可以保证序列在不同的枚举中不会发生变化,因此多次执行的方法可能是危险的(而且是低效的,这取决于序列的性质)。然而,对于大型序列来说,它仍然不是理想的解决方案。我建议您手动编写自己的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);

#3


107  

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

#4


29  

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 ?

嗯?

#5


11  

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

到目前为止的答案都很好!但我认为有必要采取以下限制措施:

  1. Plain, concise LINQ;
  2. 平原,简洁的LINQ;
  3. O(n) complexity;
  4. O(n)的复杂性;
  5. Do not evaluate the property more than once per element.
  6. 不要对属性进行多次评估。

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

#6


4  

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:

我相信,按你想要得到的那一列排序,然后抓住第一个就可以了。但是,如果有多个具有相同最大值的对象,则只有一个被捕获:

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

#7


2  

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.

这对我来说似乎很有效。

#8


1  

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.com source code)

根据卡梅隆最初的回答,我在SilverFlow图书馆的FloatingWindowHost(从FloatingWindowHost中复制)中添加了我刚刚添加的内容。在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);
    }

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.

值得注意的是在画布之上的代码。ZIndex是一个附加的属性,可以用于各种容器中的uielement,而不仅仅是在Canvas中托管的时候使用(请参阅在Silverlight中不使用Canvas控件的控制呈现顺序(ZOrder))。通过修改这个代码,您甚至可以轻松地为UIElement创建一个SetTopmost和setlowmost静态扩展方法。

#9


1  

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),这个名称将会很清楚。

#1


201  

We have an extension method to 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的第二个解决方案(基本上与MaxBy相同)更好(IMO):

  • It's O(n) unlike the previous accepted answer which finds the maximum value on every iteration (making it O(n^2))
  • 这是O(n)与先前接受答案找到最大值在每个迭代(使它O(n ^ 2))
  • The ordering solution is O(n log n)
  • 顺序解是O(n log n)
  • Taking the Max value 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.
  • 取最大值,然后找到具有该值的第一个元素是O(n),但是遍历序列两次。在可能的情况下,您应该以单通道方式使用LINQ。
  • It's a lot simpler to read and understand than the aggregate version, and only evaluates the projection once per element
  • 与聚合版本相比,阅读和理解要简单得多,并且只计算每个元素的投影。

#2


152  

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

这需要排序(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)):

请注意,这具有枚举列表序列的优势。虽然list是list ,但在此期间没有变化,但它可能对任意IEnumerable 对象很重要。没有任何东西可以保证序列在不同的枚举中不会发生变化,因此多次执行的方法可能是危险的(而且是低效的,这取决于序列的性质)。然而,对于大型序列来说,它仍然不是理想的解决方案。我建议您手动编写自己的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);

#3


107  

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

#4


29  

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 ?

嗯?

#5


11  

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

到目前为止的答案都很好!但我认为有必要采取以下限制措施:

  1. Plain, concise LINQ;
  2. 平原,简洁的LINQ;
  3. O(n) complexity;
  4. O(n)的复杂性;
  5. Do not evaluate the property more than once per element.
  6. 不要对属性进行多次评估。

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

#6


4  

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:

我相信,按你想要得到的那一列排序,然后抓住第一个就可以了。但是,如果有多个具有相同最大值的对象,则只有一个被捕获:

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

#7


2  

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.

这对我来说似乎很有效。

#8


1  

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.com source code)

根据卡梅隆最初的回答,我在SilverFlow图书馆的FloatingWindowHost(从FloatingWindowHost中复制)中添加了我刚刚添加的内容。在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);
    }

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.

值得注意的是在画布之上的代码。ZIndex是一个附加的属性,可以用于各种容器中的uielement,而不仅仅是在Canvas中托管的时候使用(请参阅在Silverlight中不使用Canvas控件的控制呈现顺序(ZOrder))。通过修改这个代码,您甚至可以轻松地为UIElement创建一个SetTopmost和setlowmost静态扩展方法。

#9


1  

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),这个名称将会很清楚。