1

So... I asked if something has changed in the last 9 years, and now the question has been closed as a duplicate of a question and answers from 10 years ago...

I know I can get the minimal Key and then use it. Like this:

theEnumerable[theEnumerable.Min(a => a.Key)];

but is there a direct way? As far as I know, there wasn't 9 years ago. But that's a long time. (Windows Phone (not "Mobile"!) had just been released then...)

(I can also OrderBy and then take the First() but that is very wasteful.)

ispiro
  • 26,556
  • 38
  • 136
  • 291
  • 2
    [MoreLINQ](https://github.com/morelinq/MoreLINQ) MinBy method – Alexander Petrov May 05 '19 at 18:44
  • Assuming your `Key` is numerical I thought .Min is the most direct way (if I'm not wrong a `.Min()` will effectively iterate through the collection). is the `IEnumerable` a huge list? Was wondering if it would be more efficient if you sort the list and just get the first. – VT Chiew May 05 '19 at 18:51
  • you want a key value pair and key is min of what? what type of elements will be in the IEnumerable, and also what do you want the value to be? – Yair I May 05 '19 at 22:06

1 Answers1

0

So you have a sequence of similar items, where every item has at least a property Key which is of a type that implements IComparable.

You want from all items in your sequence the item that has the lowest value for property Key. If there are several with the same lowest value, you don't care which one you get.

If you write it like this, the solution is simple:

var result = myKeyValuePairs.OrderBy(keyValuePair => keyValuePair.Key).FirstOrDefault();

In words: take your original sequence of keyValuePairs. Order this sequence in ascending order by value of keyValuePair.Key, from the resulting sequence take the first one, or return null if the sequence is empty.

Although this works, and although this looks very easy to understand, it is not very efficient to order the 2nd smallest, 3rd smallest, etc, if you only need the smallest one.

A more efficient method would be to use Enumerable.Aggregate. Careful though, this function only works if you are certain that there is at least one element in your sequence. If not, you should first assert that the sequence is not empty.

if (myKeyValuePairs.Any())
    return myKeyValuePairs.Aggregate( (lowestPair, currentPair) =>
           (lowestPair.Key <= currentPair.Key) ? lowestPair : currentPair);
else
    return null;

However, because of Any(), you'll enumerate the first element twice. Besides every loop you'll do an assignment.

An even more optimized code, where you enumerate the sequence exactly once, would be an extension function like:

TSource SmallestOrDefault<TSource, TResult>(this IEnumerable<TSource> source,
    Func<TSource, Tkey> keySelector)
    {
        // TODO: check null source or keySelector
        IEnumerator<TSource> enumerator = source.GetEnumerator();

        if (enumerator.MoveNext())
        {
             // sequence not empty. The first element is smallest until now
             TSource smallestElement = enumerator.Current;

             // check the rest of the sequence to see if there are smaller elements
             while (enumerator.MoveNext)
             {
                 // there is a next element. Is it smaller?
                 if (enumerator.Current < smallestElement)
                 {
                      // found a new smallest element
                      smallestElement = enumerator.Current;
                 }
             } 
             return smallestElement;
       }
       else
           // empty sequence
           return null;
    }

Usage:

var smallestElement = myKeyValuePairs.SmallestOrDefault(
      keyValuePair => keyValuePair.Key);
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116