Given such a list:
List intList = new List();
intList.Add(5);
intList.Add(10);
intList.Add(15);
I can't improve on Jon Skeet's answer for the general case, so I am going for the 'high performance' prize in the specific case of a list of ints.
public static class Extensions
{
public static int IndexOfMaximumElement(this IList<int> list)
{
int size = list.Count;
if (size < 2)
return size - 1;
int maxValue = list[0];
int maxIndex = 0;
for (int i = 1; i < size; ++i)
{
int thisValue = list[i];
if (thisValue > maxValue)
{
maxValue = thisValue;
maxIndex = i;
}
}
return maxIndex;
}
Here's how to do it in one (long) line using LINQ, with just a single pass through the collection. It should work for any IEnumerable<int>
, not just lists.
int maxIndex = intList
.Select((x, i) => new { Value = x, Index = i })
.Aggregate
(
new { Value = int.MinValue, Index = -1 },
(a, x) => (a.Index < 0) || (x.Value > a.Value) ? x : a,
a => a.Index
);
Here's the non-LINQ equivalent of the above, using a foreach
loop. (Again, just a single pass through the collection, and should work for any IEnumerable<int>
.)
int maxIndex = -1, maxValue = int.MinValue, i = 0;
foreach (int v in intList)
{
if ((maxIndex < 0) || (v > maxValue))
{
maxValue = v;
maxIndex = i;
}
i++;
}
If you know that the collection is an IList<int>
then a plain for
loop is probably the easiest solution:
int maxIndex = -1, maxValue = int.MinValue;
for (int i = 0; i < intList.Count; i++)
{
if ((maxIndex < 0) || (intList[i] > maxValue))
{
maxValue = intList[i];
maxIndex = i;
}
}
Here is a simple* and relatively efficient** solution:
int indexMax
= !intList.Any() ? -1 :
intList
.Select( (value, index) => new { Value = value, Index = index } )
.Aggregate( (a, b) => (a.Value > b.Value) ? a : b )
.Index;
The !intList.Any() ? -1 :
will force a -1
if the list is empty;
The Select
will project each int
element into an anonymous type with two properties: Value
and Index
;
The Aggregate
will get the element with the highest Value
;
Finally, we get the Index
of the chosen element.
* Simplicity is relative. The aim here was to reach a balance of readability and still only scan the list once.
** The allocation of lots of new objects during the Select
is probably wasteful. As some people tested, it doesn't perform well for large lists.
EDIT 1: empty list check added.
EDIT 2: added caveats about performance.
Here is my solution:
public static int IndexOfMax(this IList<int> source)
{
if (source == null)
throw new ArgumentNullException("source");
if (source.Count == 0)
throw new InvalidOperationException("List contains no elements");
int maxValue = source[0];
int maxIndex = 0;
for (int i = 1; i < source.Count; i++)
{
int value = source[i];
if (value > maxValue)
{
maxValue = value;
maxIndex = i;
}
}
return maxIndex;
}
this way :
var maxIndex = foo.IndexOf(foo.Max());
Here's the non-linq method if you like:
private int ReturnMaxIdx(List<int> intList)
{
int MaxIDX = -1;
int Max = -1;
for (int i = 0; i < intList.Count; i++)
{
if (i == 0)
{
Max = intList[0];
MaxIDX = 0;
}
else
{
if (intList[i] > Max)
{
Max = intList[i];
MaxIDX = i;
}
}
}
return MaxIDX;
}
This is a single pass through the list at least.
Hope this helps,
Kyle