15

Is there any way to find minimum value index more efficient/faster than this?

int minimumValueIndex = List.IndexOf(List.Min());
Kamil
  • 13,363
  • 24
  • 88
  • 183
  • 4
    Sure. Write a simple loop to find two values: the minimum value in the list, and the index at which you found it. Done. It's still linear time. Do you have reason to believe this is your bottleneck? – Anthony Pegram May 01 '13 at 17:45
  • @AnthonyPegram Sorry for late answer. Well, this is not bottleneck. I'm just working on software optimization for slow/cheap hardware and this part of code is in "hot part" of application (I'm doing this a lot). – Kamil Sep 11 '20 at 21:38
  • Make sure that the items are _(binary search)_ inserted so the list is always sorted. And use the first item. If searching is the bottleneck, take the pain on inserting. – Jeroen van Langen Jan 02 '22 at 20:35

10 Answers10

12

Yes, you can remove the overhead of List.IndexOf() by building a custom Min() extension. (Really, Enumerable.Min() should have an extension that selects the original element by key instead of selecting a transformation. This oversight is particularly painful in situations like this.)

public static int IndexOfMin(this IList<int> self)
{
    if (self == null) {
        throw new ArgumentNullException("self");
    }

    if (self.Count == 0) {
        throw new ArgumentException("List is empty.", "self");
    }

    int min = self[0];
    int minIndex = 0;

    for (int i = 1; i < self.Count; ++i) {
        if (self[i] < min) {
            min = self[i];
            minIndex = i;
        }
    }

    return minIndex;
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thanks for pretty extension. I wonder why there is no such method in .NET. – Kamil May 01 '13 at 17:52
  • @Kamil It is kind of an edge case. However, if [`Min()`](http://msdn.microsoft.com/en-us/library/bb548741.aspx) returned `TSource` instead of `TResult` you could do this with vanilla LINQ. – cdhowie May 01 '13 at 17:53
  • Sorry for silly question, but what if I want to use this with `List` and `List`? I need two extensions (some overloading), or there is a way to create extension that will work with any numeric data type? – Kamil May 01 '13 at 18:01
  • 1
    You would need two overloads, as comparison operators cannot be applied generically without some hacks. (C++ templates could handle this, but not C# generics.) – cdhowie May 01 '13 at 18:03
  • @Kamil, you could write a more generic version. One against `IEnumerable where T : struct, IComparable`, and you could keep track of the index by hand. – Anthony Pegram May 01 '13 at 18:20
  • @AnthonyPegram Any particular reason that you would choose that constraint instead of `where T : IComparable`? The use of the non-generic `IComparable` will cause primitives to be boxed when passed to `CompareTo()`. – cdhowie May 01 '13 at 18:50
  • @cdhowie, no particular reason, I was just thinking from the seat of my pants. – Anthony Pegram May 01 '13 at 19:42
5

In my own experience the LINQ aggregation methods such as Array.Max() and Array.Min() are typically slower than a manual for loop. So, you can consider something like this as an alternative approach:

int minima=0;
int mindex=0;

for(int i=0;i<List.Count;i++)
{
    if (List[i]<minima)
        {minima=List[i]; mindex=i;}
}

You can always test the speeds of both approaches on your environment by using System.Diagnostics.StopWatch.

Prahlad Yeri
  • 3,567
  • 4
  • 25
  • 55
3

At present .NET supports value tuples which allows trivial solution:

var index = List.Select((item, index) => (item, index)).Max().index;
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
2

There's a problem with answer posted by @cdhowie in that it assumes that an IList<T> can efficiently access a particular item via its indexer. While that it true for arrays and List[T], it is in nono way guaranteed (take for an example, a singly-linked list that implements Ilist<T>).

If i was going to do this in a generic, Linqy way, I'd do something like:

public static IndexOfMinValue<T>( this IList<T> list ) where T:IComparable
{
  if ( list == null ) throw new ArgumentNullException("list") ;
  int? offset = null ;
  T    min    = default(T) ;

  int i = 0 ;
  foreach ( T item in list )
  {
    if ( !offset.HasValue || item.CompareTo(min) < 0 )
    {
       offset = i ;
       min    = item ;
    }
    ++i ;
  }

  if ( !offset.HasValue ) throw new ArgumentOutOfRangeException("list","list is empty") ;
  return offset.Value ;
}

Or, arguably cleaner, since we get rid of extraneous initialization and an extraneous compare in the body of the loop:

public static int IndexOfMin<T>( this IList<T> list ) where T:IComparable
{
  if ( list == null ) throw new ArgumentNullException("list") ;

  IEnumerator<T> enumerator  = list.GetEnumerator() ;
  bool           isEmptyList = ! enumerator.MoveNext() ;

  if ( isEmptyList ) throw new ArgumentOutOfRangeException("list","list is empty") ;

  int minOffset = 0 ;
  T   minValue  = enumerator.Current ;
  for ( int i = 1 ; enumerator.MoveNext() ; ++i )
  {
    if ( enumerator.Current.CompareTo(minValue) >= 0 ) continue ;
    minValue  = enumerator.Current ;
    minOffset = i ;
  }

  return minOffset ;
}

You could also use the stock Linq Aggregate() overload, though it's no cleaner or simpler than the brute force method (probably less efficient, too, IMHO):

IList<int> = GetSomeIntegers() ;

int minIndex = list.Aggregate( (Tuple<int,int,int>)null,
  ( acc , item ) => {
    int offset     = 0    ;
    int minValue   = item ;
    int minOffset  = 0    ;
    if ( acc != null )
    {
      offset    = acc.Item3 + 1 ;
      minValue  = item < acc.Item1 ? item   : acc.Item1 ;
      minOffset = item < acc.Item1 ? offset : acc.Item2 ;
    }
    return new Tuple<int, int, int>( minValue , minOffset , offset ) ;
  }).Item2 ;
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
1

Min Calculation: Finding the Min value in a collection cannot be done faster than O(n) so it may be no better way than that but just a different in the code style.

Finding Step: depending on your problem you may use some special data structure (such as binary tree, heap tree, ...) so you can find the index more faster.

Using something like min-heap tree you can get the min value with O(1) in expense of some special Add, Remove functions.

Hossein Narimani Rad
  • 31,361
  • 18
  • 86
  • 116
1

If possible, keep track of the min value/index as the values are placed in to the list, so you do not need to loop through a full list. Then if new values are added to the list, check them against the min value that you saved, and change the new min value as necessary.

Of course that might not be suitable for your situation.

Colton
  • 1,297
  • 14
  • 27
1

I improved @cdhowie's answer a little bit to make it more powerful. If there are more than one minimum elements, then this method will return the first one.

public static T GetMin<T, TOrder>(this IEnumerable<T> self, Func<T, TOrder> orderFunc, 
                                  out int minIndex, IComparer<TOrder> cmp = null)
{
    if (self == null) throw new ArgumentNullException("self");            

    IEnumerator<T> selfEnumerator = self.GetEnumerator();
    if (!selfEnumerator.MoveNext()) throw new ArgumentException("List is empty.", "self");

    if (cmp == null) cmp = Comparer<TOrder>.Default;

    T min = selfEnumerator.Current;
    minIndex = 0;
    int intCount = 1;
    while (selfEnumerator.MoveNext ())
    {
        if (cmp.Compare(orderFunc(selfEnumerator.Current), orderFunc(min)) < 0)
        {
            min = selfEnumerator.Current;
            minIndex = intCount;
        }
        intCount++;
    }

    return min;
}
Community
  • 1
  • 1
pengdlzn
  • 33
  • 7
  • And how do you use it? I don't get why I would need a compare and order function? – weston Jan 14 '15 at 12:07
  • For example, we have a list of `Person`. `Person` has a property `Location`, and `Location` consists of `X` and `Y`. Now we want to get the person with minimum `Y`, and if there are more than one person have the same minimum `Y`, then the person with minimum `X` is what we want. In this case, we need order function to specify `Location` as the property we want to compare, and we need a compare to realize this comparison. Does my explanation make sense? – pengdlzn Jan 14 '15 at 13:12
  • Also what is `TOrder`? – weston Jan 14 '15 at 14:01
  • `TOrder` is also a [Generic Type](http://msdn.microsoft.com/en-us/library/0zk36dx2.aspx), which could be any class. In this method, `TOrder` is the class to be compared. Notice that even for one class there could be many ways to compare. For an instance of `Location`, sometimes we may want to compare them according to `Y` then `X`, but sometimes we may want to compare them according to `X` then `Y`. That is why I want to specify compare. – pengdlzn Jan 14 '15 at 14:24
  • It's so complicated. It returns something and has an `out` parameter. It has up-to two input functions. I don't think it's useful without an example or two. – weston Jan 14 '15 at 14:42
  • Yes, it's complicated, but it's powerful. I am also learning how to use order function, maybe I can make it simpler when I understand C# better. I used this method when I tried to merge ordered lists. You can find my instance [here](http://stackoverflow.com/questions/2767007/most-efficient-algorithm-for-merging-sorted-ienumerablet/27946054#27946054) – pengdlzn Jan 14 '15 at 15:23
0

Sure. Just write your own Min function, instead of using LINQ, that keeps track of the index of the current minimum value. By doing that you can avoid needing to find the index of the item based on it's min value.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

Microsoft seems don't want to rest, so they implemented another way to achieve the goal.

See MaxBy() in .NET 6.0.

It allows to find an item by max value of some key.

-3

I'm lazy so I would use:

List.Sort();/* sorts the values least to greatest */
List[0];/* first item has the least value */
user3574895
  • 121
  • 1
  • 3
  • 3
    It's not that it's wrong, but to get the minimum value from the list, the most you have to do is scan the list of n items n times. That has a O(n) complexity. But sorting is has a O( n log n ) complexity, so it's slower. And finally, what if you can't sort the array -- then you'd have to make a copy of a new array and sort that, which adds to the expense. – zumalifeguard Jul 15 '16 at 17:53