5

Possible Duplicate:
LINQ: How to perform .Max() on a property of all objects in a collection and return the object with maximum value

I have the following class:

class Product
{
    public string ProductName { get; set; }
    public DateTime ActivationDate { get; set; }
}

Then I create and fill a List<Product> and I would like to get the ProductName from the Product with the latest ActivationDate.

Product.Where(m => m.ActivationDate == Max(m.ActivationDate)).Select(n => n.ProductName)

Product.Max(m => m.ActivationDate).Select(n => n.ProductName)

but bot methods do not work. Does anybody know a way to achieve this task?

Community
  • 1
  • 1
CiccioMiami
  • 8,028
  • 32
  • 90
  • 151
  • 1
    How large is the list? It is pretty easy to write a `MaxBy` (or similar), but not worth it for small lists. For large lists, it *would* be worth it. The difference is `O(n)` vs `O(n^2)` (or maybe `O(n*log(n))` – Marc Gravell Jul 17 '12 at 10:43

6 Answers6

9

You can OrderByDescending the List<Product> on the ActivationDate Field and then take FirstOrDefault()

Product.OrderByDescending(p => p.ActivationDate).FirstOrDefault();

For a more simpler version there is an extension method

MaxBy

Product.MaxBy(p => p.ActivationDate);
V4Vendetta
  • 37,194
  • 9
  • 78
  • 82
4

If you can do this:

class Product : IComparable<Product>
{
    public string ProductName { get; set; }
    public DateTime ActivationDate { get; set; }

    public int CompareTo(Product other)
    {
        return this.ActivationDate.CompareTo(other.ActivationDate);
    }
}

Then it is just this:

var max = products.Max(p => p).ProductName;
Tomas Grosup
  • 6,396
  • 3
  • 30
  • 44
  • +1 Never thought of that. It'd be great if there was an overload of `Max` that took an IComparator so you could have different comparisons. (BTW do you mean `.ProductName` in your last line?) – Rawling Jul 17 '12 at 10:48
  • Best solution. You could omit the lambda parameter though: `var max = products.Max().ProductName`. – Herman Kan Sep 08 '18 at 06:00
2

Here we go; a single pass of the list:

public static TSource MaxBy<TSource,TValue>(
    this IEnumerable<TSource> source,
    Func<TSource,TValue> selector)
{
    using(var iter = source.GetEnumerator())
    {
        if (!iter.MoveNext())
            throw new InvalidOperationException("Empty sequence");
        var max = selector(iter.Current);
        var item = iter.Current;
        var comparer = Comparer<TValue>.Default;
        while(iter.MoveNext())
        {
            var tmp = selector(iter.Current);
            if(comparer.Compare(max, tmp) < 0)
            {
                item = iter.Current;
                max = tmp;
            }
        }
        return item;
    }
}

then:

var maxObj = list.MaxBy(x => x.SomeProp);

This is more efficient than doing an OrderBy, for example, which needs to actually sort the data, rather than just sweep over it once.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I know it is the same name as in MoreLinq, but I find it unfortunate. the important thing, which should be part of the name, is whether it returns First item with the max value, or Last item with the max value. – Tomas Grosup Jul 17 '12 at 10:55
  • 1
    Or maybe it could return all objects with the max value. – Tomas Grosup Jul 17 '12 at 10:58
  • @Marc - interesting last point. If LINQ did a selection sort, then `OrderBy.First` would essentially do the same as `MaxBy`, but you could then select the next few items too. – Rawling Jul 17 '12 at 10:58
  • @Rawling the work involved in an OrderBy is **much** more significant than just iterating over the data once – Marc Gravell Jul 17 '12 at 11:34
  • 1
    @TomasGrosup here's the wonderful thing; if you copy and paste it, I won't mind if you change the name ;p – Marc Gravell Jul 17 '12 at 11:35
  • @Tomas to get *all* values with a given value, you'd need to buffer too (at least the items that match). That *might* not be a problem, it could be in some cases. Easy to do, of course - I'm just remarking. – Marc Gravell Jul 17 '12 at 11:37
  • @Marc Exactly - if you only wanted the first few, it should be more efficient to implement a deferred-execution selection-sort based `OrderBy` and then `.Take` as many as you needed, than to do a non-deferrable quicksort-based `OrderBy`. – Rawling Jul 17 '12 at 11:47
1

How about writting an extension Function called Max which internally does the simple search logic presented by Branko Dimitrijevic.

/// <param name="comparer">Func<T current, T currentMax, long> </param>
    public static T Max<T>(this List<T> collection, Func<T, T, long> comparer) where T : class
    {
        T max_product = null;
        collection.ForEach(c =>
        {
            if (max_product == null || comparer(c, max_product) > 0)
                max_product = c;
        });

        return max_product;
    }

Call this function as:

string maxProductName = products.Max<Product>((currentProduct, currentMaxProduct) =>
        {
            // Basically any logic
            return currentMaxProduct.ActivationDate.CompareTo(currentProduct.ActivationDate);
        }).ProductName;
SSK .NET PRO
  • 126
  • 4
0

A non-LINQ solution is easy enough, in case you need it in only one place so the general MaxBy would be an overkill:

Product max_product = null;

foreach (var product in products) {
    if (max_product == null || max_product.ActivationDate < product.ActivationDate)
        max_product = product;
}

// Use `max_product`...
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
-2

Try this one

ProductList.Where(m => m.ActivationDate == ProductList.Max(pl => pl.ActivationDate)).FirstOrDefault().ProductName;
TimVK
  • 1,146
  • 6
  • 16
  • 1
    At least store the max once and then check against it. Otherwise you're looping through the list up to once for each item in the list. – Rawling Jul 17 '12 at 10:42