开发者

How can I get LINQ to return the object which has the max value for a given property? [duplicate]

This question already has answers here: 开发者_高级运维 How to perform .Max() on a property of all objects in a collection and return the object with maximum value [duplicate] (9 answers) How to use LINQ to select object with minimum or maximum property value (20 answers) Closed 7 years ago.

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.


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


.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.


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.


Use MaxBy from the morelinq project:

items.MaxBy(i => i.ID);


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


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


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


try this:

var maxid = from i in items
            group i by i.clientid int g
            select new { id = g.Max(i=>i.ID }


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


You could use a captured variable.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜