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


этот вопрос уже есть ответ здесь:

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

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

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

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

могу ли я сделать это с Linq? Как?

9 215

9 ответов:

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

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

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

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

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

для этого потребуется sort (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);

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

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

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. о(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

Я считаю, что сортировка по столбцу, который вы хотите получить максимум, а затем захватить первый, должна работать. Однако, если есть несколько объектов с одинаковым максимальным значением, будет захвачен только один:

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

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

основываясь на первоначальном ответе Кэмерона, вот что я только что добавил в своей расширенной версии библиотеки SilverFlow FloatingWindowHost (копирование из FloatingWindowHost.cs at 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);
    }

стоит отметить, что код выше этого холста.ZIndex-это вложенное свойство, доступное для UIElements в различных контейнерах, а не только для размещения на холсте (см. управление порядком рендеринга (ZOrder) в Silverlight без использования элемента управления Canvas). Думаю, можно даже сделать SetTopmost и SetBottomMost статический метод расширения для UIElement легко адаптируя этот код.

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