LINQ: как выполнить .Max() по свойству всех объектов в коллекции и вернуть объект с максимальным значением

У меня есть список объектов, которые имеют два свойства int. Список - это вывод другого запроса linq. Объект:

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

Я хочу найти и вернуть объект в списке с наибольшим значением свойства Height.

Я могу получить максимальное значение Height, но не сам объект.

Можно ли это сделать с Linq? Как?

Ответ 1

У нас есть метод расширения, чтобы сделать именно это в MoreLINQ. Вы можете посмотреть на реализацию там, но в основном это случай итерации через данные, помня о максимальном элементе, который мы видели до сих пор, и о максимальном значении, которое оно произвело при проектировании.

В вашем случае вы сделаете что-то вроде:

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

Это лучше (IMO), чем любое из представленных здесь решений, кроме второго решения Мехрдада (которое в основном совпадает с MaxBy):

  • Это O (n), в отличие от предыдущего принятого ответа , который находит максимальное значение на каждой итерации (делая его O (n ^ 2))
  • Решение для упорядочения - это O (n log n)
  • Принимая значение Max, а затем находим первый элемент с этим значением O (n), но повторяем по последовательности дважды. По возможности, вы должны использовать LINQ в однопроходном режиме.
  • Гораздо проще читать и понимать, чем сводная версия, и оценивает только один раз за элемент

Ответ 2

Для этого потребуется сортировка (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);

Ответ 3

Выполнение заказа, а затем выбор первого элемента тратит много времени на упорядочивание элементов после первого. Вы не заботитесь о порядке этих действий.

Вместо этого вы можете использовать функцию aggregate для выбора лучшего элемента на основе того, что вы ищете.

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

И почему бы вам не попробовать?

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

mmm?

Ответ 5

Ответы пока замечательные! Но я вижу необходимость в решении со следующими ограничениями:

  • Обычный, сжатый LINQ;
  • O (n) сложность;
  • Не оценивайте свойство более одного раза для каждого элемента.

Вот он:

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

Ответ 6

Я считаю, что сортировка по столбцу, который вы хотите получить 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; }
}

Ответ 7

В 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')

Что мне кажется довольно эффективным.

Ответ 8

Основываясь на исходном ответе Камерона, вот что я только что добавил в моей расширенной версии библиотеки 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 является прикрепленным свойством, доступным для UIElements в различных контейнерах, а не только при размещении на холсте (см. Контроль порядка отображения (ZOrder ) в Silverlight без использования Canvas control). Угадайте, что даже можно упростить статический метод SetTopmost и SetBottomMost для UIElement, адаптировав этот код.

Ответ 9

Вы также можете обновить решение 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);

Это имя будет понятным для людей, использующих С++ (потому что там есть std:: max_element).