Coder Perfect

How to put on a show Return the object with the highest value [duplicate] by calling Max() on a property of all objects in a collection.

Problem

I’ve got a list of objects with two integer properties. Another linq query returns a list as a result. The goal is to:

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

I want to identify and return the item with the highest Height property value in the list.

I can get the highest value of the Height parameter, but not the actual object.

Is it possible to achieve this with Linq? How?

Asked by theringostarrs

Solution #1

In MoreLINQ, we provide an extension method that does just this. You can see how it’s done there, but basically it’s a matter of iterating through the data and remembering the highest element we’ve seen so far and the highest value it created under the projection.

You’d do something like this in your case:

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

Except for Mehrdad’s second approach (which is effectively the same as MaxBy), this is better (in my opinion) than any of the other methods offered here:

Answered by Jon Skeet

Solution #2

This would necessitate a sort (O(n log n)), but it is relatively straightforward and adaptable. Another benefit is that it can be used with LINQ to SQL:

var maxObject = list.OrderByDescending(item => item.Height).First();

This has the advantage of only having to enumerate the list sequence once. While it may not matter if list is a ListT> that does not change in the meanwhile, arbitrary IEnumerableT> objects may. Nothing ensures that the sequence will remain consistent over enumerations, therefore procedures that repeat it several times can be problematic (and inefficient, depending on the nature of the sequence). For lengthy sequences, however, it is still a less than optimal option. If you have a big number of items, I recommend manually implementing your own MaxObject extension to be able to perform it in one pass without having to sort or do anything else (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;
    }
}

and apply it to:

var maxObject = list.MaxObject(item => item.Height);

Answered by mmx

Solution #3

Ordering first and then selecting the first item wastes a lot of time. Ordering the items after the first one saves a lot of time. The sequence of those doesn’t matter to you.

You can instead use the aggregate function to choose the best item based on your criteria.

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

Answered by Cameron MacFarland

Solution #4

Why don’t you give this a shot??? :

var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));

OR, to be more precise:

var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);

mmm ?

Answered by Dragouf

Solution #5

So far, the responses have been fantastic! However, I believe that a solution with the following limitations is required:

Here it is:

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

Usage:

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

Answered by meustrus

Post is based on https://stackoverflow.com/questions/1101841/how-to-perform-max-on-a-property-of-all-objects-in-a-collection-and-return-th