tags:

views:

238

answers:

7

I have a class A { public float Score; ... } and an IEnumerable<A> items and would like to find the A which has minimal score.

Using items.Min(x => x.Score) gives the minimal score and not the instance with minimal score.

How can I get the instance by iterating only once through my data?

Edit: So long there are three main solutions:

  • Writing an extension method (proposed by Svish). Pros: Easy to use and evaluates Score only once per item. Cons: Needs an extension method. (I choosed this solution for my application.)

  • Using Aggregate (proposed by Daniel Renshaw). Pros: Uses a built-in LINQ method. Cons: Slightly obfuscated to the untrained eye and calls evaluator more than once.

  • Implementing IComparable (proposed by cyberzed). Pros: Can use Linq.Min directly. Cons: Fixed to one comparer - can not freely choose comparer when performing the minimum computation.

+4  A: 

Try items.OrderBy(s => s.Score).FirstOrDefault();

Jamie Penney
Succinct, but be cautious of sorting; it's a "slow" operation. I would recommend benchmarking if the number of elements is large to make sure performance is acceptable.
Programming Hero
This does not iterate only once! It is O(n log n) and not O(n).
Danvil
Check Svish's answer. I was about to write an extension method that did exactly what Jon Skeet has already done.
Jamie Penney
+3  A: 

The quick way as I see it would be implementing IComparable for your class A (if possible ofcourse)

class A : IComparable<A>

It's a simple implementation where you write the CompareTo(A other) have a look at IEnumerable(of T).Min on MSDN for a reference guide

cyberzed
This is a good idea. But what if I had Score1 and Score2 and would like to change what score I use for finding the minimum?
Danvil
Hmm well it depends...somehow I wouldn't mind having the object knowing what defines it's comparison base, but I can see the concept of why you would like it the other way around. I would say the Jon Skeet way would be obvious with MinBy
cyberzed
A: 

Jamie Penney got my vote. You can also say

items.OrderBy(s => s.Score).Take(1);

Which has the same effect. Use Take(5) to take the lowest 5 etc.

rob_g
Sorting does not iterate only *once*!
Danvil
+2  A: 

This can be solved with a little simple iteration:

float minScore = float.MaxValue;
A minItem = null;

foreach(A item in items)
{
   if(item.Score < minScore)
       minItem = item;
}

return minItem;

It's not a nice LINQ query, but it does avoid a sorting operation and only iterates the list once, as per the question's requirements.

Programming Hero
Of course. And I would like to know if this pattern can be expressed with linq ...
Danvil
The answer is, no LINQ does not offer a `MinItem` operation. You can wrap my answer in an extension method if you wish to have something equally succinct. Alternatively, you can do it with LINQ if you make two passes of the list.
Programming Hero
+7  A: 

Have a look at the MinBy extension method in morelinq by Jon Skeet (unless I'm mistaken).

Svish
Couldn't the whole of the body of that method be replaced with this? return source.Aggregate((c, d) => comparer.Compare(selector(c), selector(d)) < 0 ? c : d);
Daniel Renshaw
@Daniel: You'd have to ask Jon Skeet about that :p Some of it is error checking though, which you of course want to have in a library like that.
Svish
I see the slight difference, that in MinBy the selector is called only once per instance, while with Aggregate it is called more often.
Danvil
@Danvil: Good point :)
Svish
Btw.: The MinBy<> operator is also supported by Rx (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx)
Novox
+9  A: 

Use Aggregate:

items.Aggregate((c, d) => c.Score < d.Score ? c : d)
Daniel Renshaw
Interesting answer. It is quite opaque though - If I didn't already know what it was doing I would be quite confused as to the intention of that code.
Jamie Penney
Jamie: I agree, it meets Danvil's requirements but it can be difficult to parse for those not familiar with functional languages like Hakell or F#.
Daniel Renshaw
@Jamie - not when the line starts with `minItem = `. It is a bit problematic because you re-implement `Min`, but I think that's a minor point.
Kobi
This is nice :) It uses only linq and iterates only once.
Danvil
+1  A: 

A little fold should do:

var minItem = items.Aggregate((acc, c) => acc.Score < c.Score? acc : c);

Ah... too slow.

Novox