313

I have a list of objects that have two int properties. The list is the output of another linq query. The object:

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

I want to find and return the object in the list which has the largest Height property value.

I can manage to get the highest value of the Height value but not the object itself.

Can I do this with Linq? How?

Uwe Keim
  • 36,867
  • 50
  • 163
  • 268
theringostarrs
  • 10,520
  • 14
  • 47
  • 62

9 Answers9

305

We have an extension method to do exactly this in MoreLINQ. You can look at the implementation there, but basically it's a case of iterating through the data, remembering the maximum element we've seen so far and the maximum value it produced under the projection.

In your case you'd do something like:

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

This is better (IMO) than any of the solutions presented here other than Mehrdad's second solution (which is basically the same as MaxBy):

  • It's O(n) unlike the previous accepted answer which finds the maximum value on every iteration (making it O(n^2))
  • The ordering solution is O(n log n)
  • Taking the Max value and then finding the first element with that value is O(n), but iterates over the sequence twice. Where possible, you should use LINQ in a single-pass fashion.
  • It's a lot simpler to read and understand than the aggregate version, and only evaluates the projection once per element
Community
  • 1
  • 1
Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • 45
    Linq extensions have already a [`Max`](http://msdn.microsoft.com/en-us/library/Bb343284(v=vs.90).aspx) method which you can use also with lambda expression `var item = items.Max(x => x.Height);` – Sebastien GISSINGER Apr 22 '14 at 13:58
  • 95
    @sgissinger: You've missed the point - that will give the maximum height within the collection, not the item *with* the maximum height. – Jon Skeet Apr 22 '14 at 14:04
  • 25
    FWIW this is the aggregate version: `items.Aggregate((i, j) => i.Height > j.Height ? i : j)` – orad Jul 19 '14 at 01:46
  • 6
    @orad: Yes, that will do it - but much less expressive, IMO. – Jon Skeet Jul 19 '14 at 08:14
  • 3
    MaxBy/MinBy did the job perfectly. +1 For the gift of MoreLinq! Thanks @JonSkeet (Added MoreLinq through NuGet by the way) – Vitox Dec 19 '14 at 07:28
  • I respectfully disagree that the code of MaxBy is more expressive. I believe with adequate exposure to functional programming, people would actually find the Aggregate method more expressive. To get around running the selector on the max element on each iteration, it could be stored as a Tuple (Item, ComparisonValue). – jam40jeff Mar 24 '15 at 14:57
  • 1
    @jam40jeff: Well, `MaxBy` says *exactly* what you want it to do. Basically no translation needed. Would you prefer to use `Aggregate` instead of `Count()` as well? That's just as feasible, but less readable IMO... – Jon Skeet Mar 24 '15 at 15:07
  • I agree that defining a MaxBy extension method is the most expressive. I was referring to the contents of the MaxBy method. I believe it would be more readable if it were implemented using the Aggregate method, but that's just an opinion. – jam40jeff Mar 24 '15 at 15:28
  • 1
    @jam40jeff: Ah, I see. I don't think I ever compared the *implementation* of `MaxBy` with the aggregate version. It's worth noting that the current implementation only executes the selector once per input item, as opposed to twice, which a naive aggregation would. In some cases that could be significant. – Jon Skeet Mar 24 '15 at 15:33
  • True, although the Tuple I mentioned could get around that (the aggregate value could be both the max item and its projection). However, this would likely lower the readability further. Both approaches are equally "valid", I was simply commenting that expressiveness is more subjective. – jam40jeff Mar 24 '15 at 15:38
  • 1
    This doesn't return a single object, but a collection. – liang Feb 23 '21 at 03:04
  • 2
    @liang: It's changed over time, by the looks of it. Certainly when I wrote the first version, it returned a single element. It looks like it changed in March 2018. (I don't have the time to go back and find all the answers I've referred to MaxBy on Stack Overflow. It looks like if you use version 2.x you'll still get the single element version.) – Jon Skeet Feb 23 '21 at 06:47
216

This would require a sort (O(n log n)) but is very simple and flexible. Another advantage is being able to use it with LINQ to SQL:

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

Note that this has the advantage of enumerating the list sequence just once. While it might not matter if list is a List<T> that doesn't change in the meantime, it could matter for arbitrary IEnumerable<T> objects. Nothing guarantees that the sequence doesn't change in different enumerations so methods that are doing it multiple times can be dangerous (and inefficient, depending on the nature of the sequence). However, it's still a less than ideal solution for large sequences. I suggest writing your own MaxObject extension manually if you have a large set of items to be able to do it in one pass without sorting and other stuff whatsoever (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 use it with:

var maxObject = list.MaxObject(item => item.Height);
mmx
  • 390,062
  • 84
  • 829
  • 778
  • 2
    Rats, didn't see this before I posted. That's basically what we do in MoreLINQ, except we iterate directly using GetEnumerator rather than having a "first" flag. I think it makes the code a bit simpler. We also allow use Comparer and allow one to be passed in, rather than requiring U to implement IComparable, but that's a side issue. – Jon Skeet Jul 09 '09 at 05:38
  • 1
    The last part is a lot of unnecessary complexity for a simple problem. – KristoferA Jul 09 '09 at 08:48
  • 6
    This is a solution for a more generic problem. Basically, you declare such an extension method to *abstract away* the complexity whenever you need it. – mmx Jul 09 '09 at 09:12
  • 6
    Isn't that how software development works? – mmx Jul 09 '09 at 09:38
  • For the sake of completeness, any reason to prefer `OrderByDescending().First()` or `OrderBy().Last()`? I'm using second option in my code (shorter, at least) because I think both whould have the same cost? (edit: well, nevermind... http://stackoverflow.com/questions/6680666/orderby-last-or-orderbydescending-first-performance) – heltonbiker Jun 17 '14 at 13:33
155

Doing an ordering and then selecting the first item is wasting a lot of time ordering the items after the first one. You don't care about the order of those.

Instead you can use the aggregate function to select the best item based on what you're looking for.

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);
Cameron MacFarland
  • 65,569
  • 20
  • 98
  • 130
  • Is this O(n) then? And how does it handle an empty list? – André C. Andersen Apr 14 '13 at 10:28
  • 13
    This should be the accepted answer. Any other method will iterate over objects more than 1 time or use an unneeded library. See also : http://stackoverflow.com/questions/3188693/how-can-i-get-linq-to-return-the-object-which-has-the-max-value-for-a-given-prop/3188804#3188804 – Seattle Leonard Nov 26 '13 at 17:48
  • maxHeightAndWidth will perform inconsistently based on the order of the list. For example if you only have {1, 3} and {3, 1}, it will return the first item in the list regardless of the order. So it's not actually useful. – meustrus Aug 06 '15 at 19:10
  • @meustrus For maxHeightAndWidth {1, 3} and {3, 1} are equivalent since there's no order on the values (height before width, or width before height). Since they're equivalent it shouldn't make a difference which is returned first. – Cameron MacFarland Aug 07 '15 at 02:00
  • Worst case this performs just as bad as MaxBy, no? MaxBy will iterate the entire list, and so will this one. – Christophe De Troyer Aug 26 '18 at 08:20
  • 2
    @ChristopheDeTroyer it's not possible to find the max value without iterating the entire list. – Koja Nov 28 '19 at 08:55
  • That was my point, indeed. – Christophe De Troyer Nov 29 '19 at 09:22
37

And why don't you try with this ??? :

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

OR more optimise :

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

mmm ?

Dragouf
  • 4,500
  • 4
  • 41
  • 51
  • 20
    Because this is O(n^2). For each item in the list, it goes through all the items and finds the one with the biggest height, then checks if the current item is the same height, and returns it. Fine for small lists, but over 100 items you will notice a difference. – Cameron MacFarland Mar 26 '12 at 04:18
  • 2
    You are right, So you must before extract items.Max(y => y.Height) from lambda expression and keep it in a variable. var itemMaxHeight = items.Max(y => y.Height); var item = items.Where(x => x.Height == itemMaxHeight); I wrote it quickly because I thought it could be an easier simple way to do it. But to optimise it's better to do that, that's true. – Dragouf Mar 26 '12 at 12:24
  • 1
    The more optimized version is now O(2n) which is better, but now requires a temporary variable and 2 lines, and still isn't O(n) like some of the other answers. *shrug* – Cameron MacFarland Mar 26 '12 at 23:49
  • 3
    are you sure that the result of items.Max(y => y.Height) won't be cached? Also I thought (as far as big-O notation is concerned) O(2n) was the same as O(n)? – hdgarrood Apr 04 '13 at 10:48
  • I have found that in LINQ to SQL the first version produces 'better' SQL. It will result in only one database call instead of two, and the execution plan looks better when the resulting SQL is ran in SQL Server Management Studio. – David Dowdle Jan 29 '14 at 14:18
  • 2
    @CameronMacFarland i want to point out that -mathematically speeaking- O(n) = O(2n), the factors that matter in complexity are exponentials and powers. (I may be expressing this in a non precise way, since ther's some time when i studied this, if some can express it better feel free to edit my post) – Souhaieb Besbes Aug 14 '15 at 10:37
30

The answers so far are great! But I see a need for a solution with the following constraints:

  1. Plain, concise LINQ;
  2. O(n) complexity;
  3. Do not evaluate the property more than once per element.

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
meustrus
  • 5,075
  • 4
  • 35
  • 48
2

I believe that sorting by the column you want to get the MAX of and then grabbing the first should work. However, if there are multiple objects with the same MAX value, only one will be grabbed:

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; }
}
regex
  • 3,494
  • 4
  • 28
  • 41
2

In NHibernate (with NHibernate.Linq) you could do it as follows:

return session.Query<T>()
              .Single(a => a.Filter == filter &&
                           a.Id == session.Query<T>()
                                          .Where(a2 => a2.Filter == filter)
                                          .Max(a2 => a2.Id));

Which will generate SQL like follows:

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

Which seems pretty efficient to me.

Tod Thomson
  • 4,513
  • 2
  • 30
  • 33
1

Based on Cameron's initial answer, here is what I've just added at my enhanced version of SilverFlow library's FloatingWindowHost (copying from FloatingWindowHost.cs at http://clipflair.codeplex.com source code)

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

Worth noting regarding the code above that Canvas.ZIndex is an attached property available for UIElements in various containers, not just used when being hosted in a Canvas (see Controlling rendering order (ZOrder) in Silverlight without using the Canvas control). Guess one could even make a SetTopmost and SetBottomMost static extension method for UIElement easily by adapting this code.

Community
  • 1
  • 1
George Birbilis
  • 2,295
  • 2
  • 25
  • 33
  • oops, sorry, didn't see you want to return the object, not the value - anyway leaving the answer here for anyone who wonders how to return the max value like I was – George Birbilis Dec 06 '13 at 19:15
1

You can also upgrade Mehrdad Afshari's solution by rewriting the extention method to faster (and better looking) one:

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

And then just use it:

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

That name will be clear to people using C++ (because there is std::max_element in there).

Maciej Oziębły
  • 1,101
  • 12
  • 12