Reputation: 67380
I have an array of doubles and I want the index of the highest value. These are the solutions that I've come up with so far but I think that there must be a more elegant solution. Ideas?
double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
int topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderByDescending(x => x.Item).Select(x => x.Index).First();
topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderBy(x => x.Item).Select(x => x.Index).Last();
double maxVal = score.Max();
topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).Where(x => x.Item == maxVal).Select(x => x.Index).Single();
Upvotes: 31
Views: 47045
Reputation: 3083
This isn't the only Aggregate based solution, but this is really just a single line solution.
double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
var max = score.Select((val,ix)=>new{val,ix})
.Aggregate(new{val=Double.MinValue,ix=-1},(z,last)=>z.val>=last.val?z:last);
Console.WriteLine ("maximum value is {0}", max.val );
Console.WriteLine ("index of maximum value is {0}", max.ix );
Upvotes: 2
Reputation: 3203
Try this one which is completely LINQ and has the best performance:
var indexAtMax = scores.Select((x, i) => new { x, i })
.Aggregate((a, a1) => a.x > a1.x ? a : a1).i;
Upvotes: 4
Reputation: 11
System.Linq.Enumerable.Select
with index and System.Linq.Enumerable.Aggregate
would do it in one line
public static int IndexOfMax<TSource>(this IEnumerable<TSource> source)
where TSource : IComparable<TSource> => source.Select((value, idx) => (value, idx))
.Aggregate((aggr, next) => next.value.CompareTo(aggr.value) > 0 ? next : aggr).idx;
Upvotes: 1
Reputation: 61
If you want something that looks LINQy, in that it's purely functional, then Jon Skeets' answer above can be recast as:
public static int MaxIndex<T>(this IEnumerable<T> sequence) where T : IComparable<T>
{
return sequence.Aggregate(
new { maxIndex = -1, maxValue = default(T), thisIndex = 0 },
((agg, value) => (value.CompareTo(agg.maxValue) > 0 || agg.maxIndex == -1) ?
new {maxIndex = agg.thisIndex, maxValue = value, thisIndex = agg.thisIndex + 1} :
new {maxIndex = agg.maxIndex, maxValue = agg.maxValue, thisIndex = agg.thisIndex + 1 })).
maxIndex;
}
This has the same computational complexity as the other answer, but is more profligate with memory, creating an intermediate answer for each element of the enumerable.
Upvotes: 0
Reputation: 3015
The worst possible complexity of this is O(2N) ~= O(N), but it needs to enumerate the collection two times.
void Main()
{
IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5 };
int max = numbers.Max ();
int index = -1;
numbers.Any (number => { index++; return number == max; });
if(index != 4) {
throw new Exception("The result should have been 4, but " + index + " was found.");
}
"Simple test successful.".Dump();
}
Upvotes: 0
Reputation: 787
Meh, why make it overcomplicated? This is the simplest way.
var indexAtMax = scores.ToList().IndexOf(scores.Max());
Yeah, you could make an extension method to use less memory, but unless you're dealing with huge arrays, you will never notice the difference.
Upvotes: 52
Reputation: 27
I had this problem today (to get the index in a users array who had highest age), and I did on this way:
var position = users.TakeWhile(u => u.Age != users.Max(x=>x.Age)).Count();
It was on C# class, so its noob solution, I´am sure your ones are better :)
Upvotes: 1
Reputation: 1503869
I suggest writing your own extension method (edited to be generic with an IComparable<T>
constraint.)
public static int MaxIndex<T>(this IEnumerable<T> sequence)
where T : IComparable<T>
{
int maxIndex = -1;
T maxValue = default(T); // Immediately overwritten anyway
int index = 0;
foreach (T value in sequence)
{
if (value.CompareTo(maxValue) > 0 || maxIndex == -1)
{
maxIndex = index;
maxValue = value;
}
index++;
}
return maxIndex;
}
Note that this returns -1 if the sequence is empty.
A word on the characteristics:
As for whether this "is LINQ" or not: if it had been included as one of the standard LINQ query operators, would you count it as LINQ? Does it feel particularly alien or unlike other LINQ operators? If MS were to include it in .NET 4.0 as a new operator, would it be LINQ?
EDIT: If you're really, really hell-bent on using LINQ (rather than just getting an elegant solution) then here's one which is still O(n) and only evaluates the sequence once:
int maxIndex = -1;
int index=0;
double maxValue = 0;
int urgh = sequence.Select(value => {
if (maxIndex == -1 || value > maxValue)
{
maxIndex = index;
maxValue = value;
}
index++;
return maxIndex;
}).Last();
It's hideous, and I don't suggest you use it at all - but it will work.
Upvotes: 48
Reputation:
var scoreList = score.ToList();
int topIndex =
(
from x
in score
orderby x
select scoreList.IndexOf(x)
).Last();
If score
wasn't an array this wouldn't be half bad...
Upvotes: 13