views:

131

answers:

8

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.

+1  A: 
.orderbydescending(i=>i.id).take(1)
Codism
Works, but it's nlogn instead of linear time.
tzaman
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
+5  A: 

Use MaxBy from the morelinq project:

items.MaxBy(i => i.ID);
tzaman
@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
@Jon: Yeah - I always forget that one - I keep using it thinking it's correct, too
Reed Copsey
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
@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
+1  A: 

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
+2  A: 
int max = items.Max(i => i.ID);
var item = items.Where(x => x.ID == max);
NickLarsen
"Where" returns all items with the max value, maybe we want only the first, in that case "First" would be the best
digEmAll
True, and you could be sure that `First` will return a value unless the enumerable was modified by another thread.
NickLarsen
+1  A: 

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
+6  A: 

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
I upvoted this because I like the concept. I have no idea if the this code will actually do what was asked however.
NickLarsen
@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
A: 

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;
    }
}
Gnafoo
A: 

You could use a captured variable.

Item result = items.FirstOrDefault();
items.ForEach(x =>
{
  if(result.ID < x.ID)
    result = x;
});
David B