Как выполнить .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);

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

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

Другие советы

Для этого потребуется сортировка (O(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, а затем захват первого должна работать.Однако если существует несколько объектов с одинаковым значением 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')

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

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

Вы также можете обновить решение Мехрдада Афшари, переписав метод расширения на более быстрый (и более красивый):

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