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

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

本问题已经有最佳答案,请猛点这里访问。

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

1
2
3
4
5
public class DimensionPair  
{
    public int Height { get; set; }
    public int Width { get; set; }
}

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

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

我可以用Linq做到这一点吗? 怎么样?


我们有一个扩展方法,可以在MoreLINQ中完成此操作。您可以查看那里的实现,但基本上是迭代数据的情况,记住我们到目前为止看到的最大元素以及它在投影下产生的最大值。

在你的情况下你会做类似的事情:

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

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

  • 它是O(n)不同于先前接受的答案,它在每次迭代中找到最大值(使其为O(n ^ 2))
  • 订购解决方案是O(n log n)
  • Max值,然后找到具有该值的第一个元素是O(n),但迭代序列两次。在可能的情况下,您应该以单通方式使用LINQ。
  • 阅读和理解比聚合版本简单得多,并且每个元素只评估一次投影


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

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

请注意,这具有仅枚举list序列一次的优点。虽然listList< T >并且在此期间不会发生变化可能无关紧要,但对于任意IEnumerable< T >对象可能很重要。没有什么能保证序列在不同的枚举中不会改变,因此多次执行它的方法可能是危险的(并且效率低,取决于序列的性质)。然而,对于大型序列来说,它仍然不是理想的解决方案。我建议手动编写自己的MaxObject扩展名,如果你有一大堆项目可以在一次传递中完成,而不需要排序和其他任何东西(O(n)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
    }
}

并使用它:

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


进行订购然后选择第一项是浪费了大量时间在第一项之后订购商品。你不关心那些的顺序。

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

1
2
3
4
5
6
7
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);


你为什么不尝试这个? :

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

或者更优化:

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

嗯?


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

  • 简洁明了的LINQ;
  • O(n)复杂性;
  • 不要每个元素多次评估该属性。
  • 这里是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }

    用法:

    1
    2
    3
    4
    5
    6
    7
    8
    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值的对象,则只会抓取一个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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)中,您可以按如下方式执行此操作:

    1
    2
    3
    4
    5
    return session.Query< T >()
                  .Single(a => a.Filter == filter &&
                               a.Id == session.Query< T >()
                                              .Where(a2 => a2.Filter == filter)
                                              .Max(a2 => a2.Id));

    这将产生如下SQL:

    1
    2
    3
    4
    5
    6
    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')

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


    您还可以通过将扩展方法重写为更快(更好看)的方法来升级Mehrdad Afshari的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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;
        }
    }

    然后使用它:

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

    使用C ++的人会清楚这个名称(因为那里有std :: max_element)。


    根据Cameron的初步答案,以下是我刚刚在我的SilverFlow库的FloatingWindowHost增强版中添加的内容(从http://clipflair.codeplex.com源代码中的FloatingWindowHost.cs复制)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
        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是一个附加属性,可用于各种容器中的UIElements,而不仅仅是在Canvas中托管时使用(请参阅在Silverlight中控制渲染顺序(ZOrder)而不使用Canvas控件)。猜测一个人甚至可以通过调整这个代码轻松地为UIElement制作一个SetTopmost和SetBottomMost静态扩展方法。