8

Here is some sample code I have basically written thousands of times in my life:

// find bestest thingy
Thing bestThing;
float bestGoodness = FLOAT_MIN;
foreach( Thing x in arrayOfThings )
{
  float goodness = somefunction( x.property, localvariable );
  if( goodness > bestGoodness )
  {
    bestGoodness = goodness;
    bestThing = x;
  }
}
return bestThing;

And it seems to me C# should already have something that does this in just a line. Something like:

return arrayOfThings.Max( delegate(x)
  { return somefunction( x.property, localvariable ); });

But that doesn't return the thing (or an index to the thing, which would be fine), that returns the goodness-of-fit value.

So maybe something like:

var sortedByGoodness = from x in arrayOfThings 
  orderby somefunction( x.property, localvariable ) ascending 
  select x;
return x.first;

But that's doing a whole sort of the entire array and could be too slow.

Does this exist?

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
Jamie Fristrom
  • 341
  • 4
  • 11
  • 3
    possible duplicate of [How to use LINQ to select object with minimum or maximum property value](http://stackoverflow.com/questions/914109/how-to-use-linq-to-select-object-with-minimum-or-maximum-property-value) – porges Mar 22 '12 at 00:09

4 Answers4

3

This is what you can do using System.Linq:

var value = arrayOfThings
    .OrderByDescending(x => somefunction(x.property, localvariable))
    .First();

If the array can be empty, use .FirstOrDefault(); to avoid exceptions.

You really don't know how this is implemented internally, so you can't assure this will sort the whole array to get the first element. For example, if it was linq to sql, the server would receive a query including the sort and the condition. It wouldn't get the array, then sort it, then get the first element.

In fact, until you don't call First, the first part of the query isn't evaluated. I mean this isn't a two steps evaluation, but a one step evaluation.

var sortedValues =arrayOfThings
  .OrderByDescending(x => somefunction(x.property, localvariable));
// values isn't still evaluated
var value = sortedvalues.First();
// the whole expression is evaluated at this point.
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • You can't OrderBy an arbitrary function in Linq2SQL - it has to be an expression that can be trivially translated to a SQL ORDER BY clause. So I think it's safe to assume we are limiting the scope of this question to Linq2Objects, and therefore the sorting of the enumerable could be an issue – Mike Chamberlain Mar 22 '12 at 05:18
2

I don't think this is possible in standard LINQ without sorting the enuermable (which is slow in the general case), but you can use the MaxBy() method from the MoreLinq library to achieve this. I always include this library in my projects as it is so useful.

http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/MaxBy.cs

(The code actually looks very similar to what you have, but generalized.)

Mike Chamberlain
  • 39,692
  • 27
  • 110
  • 158
  • 2
    You can actually do it without ordering in plain LINQ by using `Aggregate`, but it ends up being really ugly in all but the simplest of cases. Much better to use some sort of `MaxBy` extension method instead. – LukeH Mar 22 '12 at 00:32
1

I would implement IComparable<Thing> and just use arrayOfThings.Max().

Example here: http://msdn.microsoft.com/en-us/library/bb347632.aspx

I think this is the cleanest approach and IComparable may be of use in other places.

UPDATE

There is also an overloaded Max method that takes a projection function, so you can provide different logic for obtaining height, age, etc.

http://msdn.microsoft.com/en-us/library/bb534962.aspx

Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • 1
    But what if there is no one true way to compare Things? For instance, if the Things were people, you might implement IComparable to work over their heights. But then what happens if in the future you then want to compare on ages? – Mike Chamberlain Mar 22 '12 at 00:50
  • Also, this has the same problem as the Max() in the OP; it returns the value, not the thing or an index to the thing. – Jamie Fristrom Mar 22 '12 at 17:24
0

I followed the link Porges listed in the comment, How to use LINQ to select object with minimum or maximum property value and ran the following code in LINQPad and verified that both LINQ expressions returned the correct answers.


void Main()
{
    var things = new Thing [] {
        new Thing { Value = 100 },
        new Thing { Value = 22 },
        new Thing { Value = 10 },
        new Thing { Value = 303 },
        new Thing { Value = 223}
    };

    var query1 = (from t in things
                orderby GetGoodness(t) descending 
                select t).First();

    var query2 = things.Aggregate((curMax, x) => 
        (curMax == null || (GetGoodness(x) > GetGoodness(curMax)) ? x : curMax));   
}

int GetGoodness(Thing thing)
{
    return thing.Value * 2;
}

public class Thing 
{
    public int Value {get; set;}
}


Result from LinqPad

Results from LinqPad

Community
  • 1
  • 1
Chansik Im
  • 1,473
  • 8
  • 13