197

If I have a class that looks like:

public class Item
{
    public int ClientID { get; set; }
    public int ID { get; set; }
}

And a collection of those items...

List<Item> items = getItems();

How can I use LINQ to return the single "Item" object which has the highest ID?

If I do something like:

items.Select(i => i.ID).Max(); 

I'll only get the highest ID, when what I actually want returned is the Item object itself which has the highest ID? I want it to return a single "Item" object, not an int.

FrankTheTank
  • 2,133
  • 2
  • 14
  • 8
  • in addition to the answers on this page i thought it worth mentioning this answer too: http://stackoverflow.com/a/1101979/4880924 can anyone answer better than Jon Skeet? Provides reasoning for his answer. – BenKoshy Nov 17 '16 at 22:58

10 Answers10

214

This will loop through only once.

Item biggest = items.Aggregate((i1,i2) => i1.ID > i2.ID ? i1 : i2);

Thanks Nick - Here's the proof

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<Item> items1 = new List<Item>()
        {
            new Item(){ ClientID = 1, ID = 1},
            new Item(){ ClientID = 2, ID = 2},
            new Item(){ ClientID = 3, ID = 3},
            new Item(){ ClientID = 4, ID = 4},
        };
        Item biggest1 = items1.Aggregate((i1, i2) => i1.ID > i2.ID ? i1 : i2);

        Console.WriteLine(biggest1.ID);
        Console.ReadKey();
    }


}

public class Item
{
    public int ClientID { get; set; }
    public int ID { get; set; }
}  

Rearrange the list and get the same result

Seattle Leonard
  • 6,548
  • 3
  • 27
  • 37
  • 1
    I upvoted this because I like the concept. I have no idea if the this code will actually do what was asked however. – Nick Larsen Jul 06 '10 at 17:50
  • @Nick: Yes, this will work. See [Aggregate](http://www.bleevo.com/2009/02/system-linq-enumerable-aggregate-better-know-an-extension-method-part-1/) and [reduce](http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29) – BlueRaja - Danny Pflughoeft Jul 06 '10 at 19:39
  • 1
    I have had this same problem occur more than once, and this is the most elegant solution I have found. Thanks! – Justin R. Apr 11 '12 at 21:03
  • 3
    Well, `Item itemBig = items1.Count() > 0 ? items1.Aggregate((i1, i2) => i1.ID > i2.ID ? i1 : i2) : null;` – ruffin Nov 22 '14 at 18:22
  • 5
    @ruffin I would use items1.Any(). That way you don't iterate the entire collection. – Seattle Leonard Feb 24 '15 at 19:46
  • [Good call](http://firebreaksice.com/use-any-instead-of-count-to-see-if-an-ienumerable-has-any-objects/). Thanks. Unf can't upvote twice. ;^) – ruffin Feb 24 '15 at 20:42
  • 1
    This should be the accepted answer. It works perfectly, as you get to control how the "aggregation" happens you can simply always just return the instance with the highest/lowest thing being measured, or for that matter, any other comparison you like. – Jez Dec 27 '20 at 18:21
  • For this to be safer add null and empty array suuport: `Item biggest1 = items1?.Aggregate(default(Item), (i1, i2) => i1.ID > i2.ID ? i1 : i2);` `Console.WriteLine(biggest1?.ID);` – Edgars Pivovarenoks Aug 01 '22 at 13:30
113
.OrderByDescending(i=>i.id).First()

Regarding the performance concern, it is very likely that this method is theoretically slower than a linear approach. However, in reality, most of the time we are not dealing with the data set that is big enough to make any difference.

If performance is a main concern, Seattle Leonard's answer should give you linear time complexity. Alternatively, you may also consider to start with a different data structure that returns the max value item at constant time.

First() will do the same as Take(1) but returns the item directly instead of an enumeration containing the item.

miThom
  • 373
  • 2
  • 11
Codism
  • 5,928
  • 6
  • 28
  • 29
  • 16
    Works, but it's nlogn instead of linear time. – tzaman Jul 06 '10 at 17:36
  • 4
    tzaman: *in theory*, the LINQ system could identify the "orderby().take()" pattern and use a linear-time algorithm -- but you're right that it probably doesn't. – Gabe Jul 06 '10 at 17:54
  • 46
    `.OrderByDescending(i=>i.id).First()` would return the object itself, rather than an enumeration with 1 item. – steve cook Jun 04 '14 at 01:53
  • +1 because it's the best mix between a clear answer and an efficient answer that does not require adding an other dependency. – Mathieu VIALES Nov 09 '17 at 09:32
  • 2
    I know this is super old, but @stevecook `.FirstOrDefault()` is probably a better choice, since it's a little less prone to issues. – Dortimer May 01 '19 at 16:55
  • @Dortimer what kind of issues? i always choose First() over FirstOrDefault() where possible – symbiont Jun 15 '19 at 00:20
  • 1
    @symbiont if no elements are returned it throws an invalid operation exception. Think of it like using TryParse vs ordinary casting. It’s just safer. – Dortimer Jun 16 '19 at 15:39
  • 7
    @Dortimer if that's a valid error, then that's the opposite of safer. it should be brought to everyone's attention. "Error hiding" is a bad habit (an anti-pattern). i occasionally still use FirstOrDefault() when i expect one element, but it's only to be able produce a more informative exception message than the default – symbiont Jun 16 '19 at 19:35
  • 1
    @symbiont that’s kinda dumb. It’s about failing gracefully. FirstOrDefault() does the same exact thing, except that you can deal with bad data or an exception. First() breaks painfully, and if you’re processing a lot of information there’s a lot of room for error. That is true for any application in the wild. – Dortimer Jun 16 '19 at 20:34
  • @Dortimer. no. then we are on the same page. FirstOrDefault() instead of First(), by itself does not solve anything – symbiont Jun 18 '19 at 19:06
  • @stevecook I edited this to reflect what you said. It errors in several different contexts otherwise. – Ash Blue Jan 01 '21 at 23:31
  • @AshBlue .First does not take an integer argument, it takes either no arguments, or a predicate that is used to find the first that satisfies a certain condition. You are meaning to use Take(1) instead of First() – miThom May 25 '22 at 10:08
40
int max = items.Max(i => i.ID);
var item = items.First(x => x.ID == max);

This assumes there are elements in the items collection of course.

Nick Larsen
  • 18,631
  • 6
  • 67
  • 96
  • 1
    "Where" returns all items with the max value, maybe we want only the first, in that case "First" would be the best – digEmAll Jul 06 '10 at 17:45
  • 1
    +1 for the clarity; I'm in the camp tzaman describes above (and may try morelinq anyway too...) – goodeye Dec 22 '11 at 00:20
  • This code returns item with max ID. As Jon Skeet said, Max returns the maximal value, not the item containing the maximal value. – SeyedPooya Soofbaf Sep 03 '15 at 07:16
  • immediately readable, and requires no new lib/extension – Don Cheadle Mar 25 '16 at 17:32
  • 4
    This answer does unnecessary work. The list is completely iterated over in the first call to `Max`. The next call to `First` will perform another iteration over the list to find the element. – antiduh Nov 28 '16 at 23:39
  • Whilst I use morelinq in my projects, I have one single position in my actual code, where I need this very functionality, so i decided to go with this solution instead of referencing external libraries... So i my eyes, if your lists aren't too big, and you need the function only once this is a good alternative... – dba Sep 27 '17 at 10:28
39

Use MaxBy from the morelinq project:

items.MaxBy(i => i.ID);
tzaman
  • 46,925
  • 11
  • 90
  • 115
  • 10
    @Reed: I guess you've worked out why now... but for other readers: Max returns the maximal value, not the item *containing* the maximal value. Note that `MaxBy` is in System.Interactive in the Reactive Extensions framework too. – Jon Skeet Jul 06 '10 at 17:40
  • 1
    @Jon: Yeah - I always forget that one - I keep using it thinking it's correct, too – Reed Copsey Jul 06 '10 at 17:43
  • 1
    No need to write your own method (or add another dependency); Seattle Leonard and NickLarson both give simple one-liners which do the same thing. – BlueRaja - Danny Pflughoeft Jul 06 '10 at 19:36
  • 2
    @BlueRaja: `morelinq` has so many useful functions I throw it in on almost every project anyway. :) Also, I find `MaxBy` to be much clearer in intent than the equivalent `Aggregate` syntax - decreasing mental parse-time is always beneficial later down the line. Sure, anyone with functional experience will recognize the fold just as fast, but not everyone has that. Finally, NickLarsen's solution does two passes (and has a problem if there are multiple max-values). – tzaman Jul 07 '10 at 08:17
  • @tzaman Your solution would have the same problem as NickLarsen's if there are multiple max-values, wouldn't it? At some point, the code needs to pick which one is "correct" if there are multiple with the max-value. – Don Cheadle Mar 25 '16 at 17:30
  • @mmcrae The problem, if you look at Nick's original code at the time of my comment, was that it used a `Where` clause instead of `First`, which would return a list of everything that matched the max-value instead of a single item. It has since been edited to have the same behavior as `MaxBy`. – tzaman Mar 25 '16 at 17:46
10

This is an extension method derived from @Seattle Leonard 's answer:

 public static T GetMax<T,U>(this IEnumerable<T> data, Func<T,U> f) where U:IComparable
 {
     return data.Aggregate((i1, i2) => f(i1).CompareTo(f(i2))>0 ? i1 : i2);
 }
Paul Richards
  • 1,181
  • 1
  • 10
  • 29
6

In case you don't want to use MoreLINQ and want to get linear time, you can also use Aggregate:

var maxItem = 
  items.Aggregate(
    new { Max = Int32.MinValue, Item = (Item)null },
    (state, el) => (el.ID > state.Max) 
      ? new { Max = el.ID, Item = el } : state).Item;

This remembers the current maximal element (Item) and the current maximal value (Item) in an anonymous type. Then you just pick the Item property. This is indeed a bit ugly and you could wrap it into MaxBy extension method to get the same thing as with MoreLINQ:

public static T MaxBy(this IEnumerable<T> items, Func<T, int> f) {
  return items.Aggregate(
    new { Max = Int32.MinValue, Item = default(T) },
    (state, el) => {
      var current = f(el.ID);
      if (current > state.Max) 
        return new { Max = current, Item = el };
      else 
        return state; 
    }).Item;
}
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
5

Or you can write your own extension method:

static partial class Extensions
{
    public static T WhereMax<T, U>(this IEnumerable<T> items, Func<T, U> selector)
    {
        if (!items.Any())
        {
            throw new InvalidOperationException("Empty input sequence");
        }

        var comparer = Comparer<U>.Default;
        T   maxItem  = items.First();
        U   maxValue = selector(maxItem);

        foreach (T item in items.Skip(1))
        {
            // Get the value of the item and compare it to the current max.
            U value = selector(item);
            if (comparer.Compare(value, maxValue) > 0)
            {
                maxValue = value;
                maxItem  = item;
            }
        }

        return maxItem;
    }
}
ollb
  • 1,453
  • 1
  • 11
  • 17
3

In LINQ you can solve it the following way:

Item itemMax = (from i in items
     let maxId = items.Max(m => m.ID)
     where i.ID == maxId
     select i).FirstOrDefault();
Ohlin
  • 4,068
  • 2
  • 29
  • 35
3

try this:

var maxid = from i in items
            group i by i.clientid int g
            select new { id = g.Max(i=>i.ID }
Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
1

You could use a captured variable.

Item result = items.FirstOrDefault();
items.ForEach(x =>
{
  if(result.ID < x.ID)
    result = x;
});
Amy B
  • 108,202
  • 21
  • 135
  • 185