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

StackOverflow https://stackoverflow.com/questions/1101841

  •  12-09-2019
  •  | 
  •  

这个问题在这里已经有答案了:

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

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

我想找到并返回列表中最大的对象 Height 适当的价值。

我可以设法获得最高的价值 Height 值而不是对象本身。

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

有帮助吗?

解决方案

我们有一个 扩展方法 正是这样做的 更多LINQ. 。您可以查看那里的实现,但基本上这是一个迭代数据的情况,记住到目前为止我们看到的最大元素以及它在投影下产生的最大值。

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

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

这比 Mehrdad 的第二个解决方案(基本上与 MaxBy):

  • 它的 O(n) 与 之前接受的答案 它在每次迭代中找到最大值(使其 O(n^2))
  • 排序解决方案是 O(n log n)
  • 采取 Max value,然后找到具有该值的第一个元素的时间复杂度为 O(n),但会迭代序列两次。如果可能,您应该以单遍方式使用 LINQ。
  • 它比聚合版本更容易阅读和理解,并且每个元素只计算一次投影

其他提示

这需要排序 (O(n 日志 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);

嗯?

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

  1. 简单、简洁的 LINQ;
  2. O(n) 复杂度;
  3. 每个元素的属性评估次数不得超过一次。

这里是:

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 值的对象,则只会抓取一个:

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 的增强版本中添加的内容(从 FloatingWindowHost.cs 复制,位于 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);
    }

关于上面的代码值得注意的是 Canvas.ZIndex 是可用于各种容器中的 UIElement 的附加属性,而不仅仅是在 Canvas 中托管时使用(请参阅 不使用 Canvas 控件在 Silverlight 中控制渲染顺序 (ZOrder))。我猜想,通过修改此代码,甚至可以轻松地为 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 )。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top