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

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

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

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

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

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

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

9 ответов


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

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

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

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

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

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