views:

418

answers:

7

I'm working on a problem set at college where were using c#. I have an array of integers (apologies first off if I don't know all the problem terms) and need to find the position in array of the maximum number (along with the minimum). I have it working but it doesn't seem a very good way to do it. Can anyone suggest a better way to achive what I have?

Heres my code:

int[] usageHours = { 3, 3, 5, 4, 0, 0, 2, 2, 4, 25, 158, 320, 212, 356, 401, 460, 480, 403, 298, 213, 102, 87, 34, 45 };
double myAverage = usageHours.Average();
int runningTotal = 0;
int runningMaxPosition = 0;

for (int i = 0; i < usageHours.Length; i++)
{
    if (usageHours[i] > runningTotal)
    {
        runningMaxPosition = i;
        runningTotal = usageHours[i];
    }
}

txtmax.Text = Convert.ToString(runningMaxPosition)+" With: "+Convert.ToString(runningTotal)+" Users";
txtAv.Text = Convert.ToString(myAverage);

I hope this is readable enough, its not particularly neat as i always make it readable once its working well.

A: 

scratch the linq code, it didnt work the way you wanted

you could make your code a little bit more concise

for (int i = 0; i < usageHours.Length; i++)
{
    if (usageHours[i] > usageHours[runningMaxPosition])
        runningMaxPosition = i;
}

all it does differently is leavs out the temporary runningTotal variable.

John Boker
He needs index of max and min, not only their values
Alex Reitbort
Doesn't this return the min and max *values*, and not the array indices?
Ken White
I think you're both right, let me change my answer.
John Boker
This gets the max and min value, but not the index of those values.
NerdFury
changed to something more likely to be a better answer.
John Boker
You could then use ((IList<int>)usageHours).IndexOf to find the indices of both values. However, I'm betting that because it is a college problem, they want you to demonstrate an algorithm with looping and thus not use the built-in Linq functions to find the values.
Travis Heseman
+1  A: 

The code looks OK for finding the max value. If you are using C# 3 or later you could use the LINQ extension methods (there are Min, Max and Average methods, and on List there is also a FindIndex method, amongst others), but I get the impression that you are learning programming, and then it is sometimes a good idea to implement stuff that may be built into the framework, just for the learning value.

Fredrik Mörk
+1 because you mention learning the programming over using the framework. The problem being solved has a runtime of O(n). All nodes need to be checked in the array. It's good to understand what the Linq methods are doing, and what code doesn't need to be written because of them. I'm guessing the course wants the students to understand how to find the data points, not understand what functions exist to find the data points.
NerdFury
A: 

How about this:

double average = usageHours.Average();
int maxPosition = Enumerable.Range(0, usageHours.Length).Max(i => usageHours[i]);
int minPosition = Enumerable.Range(0, usageHours.Length).Min(i => usageHours[i]);
Igor ostrovsky
You have to learn to walk before you can run. This is too advanced for a beginner.
Jon B
This is O(3n), looping over the array 3 times. I know I have an unhealthy obsession with performance, but don't we all?
Andrew
+8  A: 

That code is mostly fine. I'd suggest changing the variable names a bit, but that's all. You can work out the minimum in the same loop. I've changed the "if" conditions very slightly to guarantee that they always pick out at least one element (even if all the values are, say, int.MinValue). There are other ways of approaching this, but this is one example. If you have an empty array, you'll end up with max=min=0, and both indexes=-1.

int currentMax = 0;
int currentMaxIndex = -1;
int currentMin = 0;
int currentMinIndex = -1;

for (int i = 0; i < usageHours.Length; i++)
{
    if (currentMaxIndex == -1 || usageHours[i] > currentMax)
    {
        currentMaxIndex = i;
        currentMax = usageHours[i];
    }
    if (currentMinIndex == -1 || usageHours[i] < currentMin)
    {
        currentMinIndex = i;
        currentMin = usageHours[i];
    }
}

Here's an alternative using nullable value types to represent "there were no values" answers:

int currentMax? = null;
int currentMaxIndex? = null;
int currentMin? = null;
int currentMinIndex? = null;

for (int i = 0; i < usageHours.Length; i++)
{
    if (currentMax == null || usageHours[i] > currentMax.Value)
    {
        currentMax = i;
        currentMax = usageHours[i];
    }
    if (currentMin == null || usageHours[i] < currentMin.Value)
    {
        currentMinIndex = i;
        currentMin = usageHours[i];
    }
}

Don't worry if you haven't come across nullable value types yet though...

Jon Skeet
+1, not only this is very instructive and helps understanding the algorithm but it is also the fastest method: O(n)
Darin Dimitrov
This is the fastest method. The suggested Linq approaches run multiple loops over the same array which makes them less efficient, but with a modern computer and on such a small array you won't notice any difference.
Andrew
+1 smart to use minvalue and maxvalue instead of 0, incase all numbers in array were negative
TStamper
@TStamper: It still wasn't quite right though. Fixed now :)
Jon Skeet
@Jon Skeet- why would you make it 0, I think they should be switch.if all number were negative, the max value would end up 0 which would be incorrect, instead of having that extra OR statement
TStamper
shouldnt the currentmin by default be the Maxvalue and the currentmax by default be the minvalue?
TStamper
@TStamper: Nope, note the condition at the start of each "if" statement - on the first go round, we always take the first value.
Jon Skeet
I'd use `int?` (that is, `Nullable<int>`) and let `null` be your sentinel value. Additionally, `null` seems like a reasonable answer for the max of an empty array.
Daniel Pryden
@Jon Skeet- Thats why I said instead of having that extra "OR" condition, the first statement in both "if" would be unnecessary
TStamper
@TStamper: I see. Sorry, your bit about "the max value would end up 0" made me miss the bit about the OR statement. It will currently give the right values regardless. Also, currently the code can distinguish between an empty array and an array with values which are either just `int.MinValue` or `int.MaxValue` - you'd have to write extra code for that otherwise (to get an appropriate index).
Jon Skeet
@Daniel: I like that idea in general - but I wonder whether it's a bit complicated for the OP. I'll edit to suggest it as an alternative.
Jon Skeet
A: 

Your code isn't bad, but it won't work if all the values are less than zero.

Try this:

int getArrayMaxPosition (double[] theArray) 
{    
    double maxVal = theArray[0];
    int ret = 0;
    int currentIndex = 0;

    foreach (double aValue in theArray) 
    {
        if (aValue > maxVal)
        {
             ret = currentIndex;
             maxVal = avalue;
        }
        currentIndex++;
    }

    return ret;
 }
jfawcett
A: 

As was mentioned on the comment's to Jon's answer, Jon's solution really is the best, most direct, quickest way of doing it.

If, however, you did want to use Igor's solution, here's the rest of it (to get the actual positions as well as the values):

int maxValue = Enumerable.Range(0, usageHours.Length).Max(i => usageHours[i]);
int maxPosition = Array.FindIndex(usageHours, i => i == maxValue);
int minValue = Enumerable.Range(0, usageHours.Length).Min(i => usageHours[i]);
int minPosition = Array.FindIndex(usageHours, i => i == minValue);
Cam Soper
+1  A: 

I just wanted to provide one-liner solution for the question (for completeness). In the OP's original question he only asks for index of the maximum and index of the minimum.

Let's stick to this question. This is the most interesting question because to find maximum value we can simply use Enumerable.Max LINQ method. The same goes for Min and Average.

Let's only provide index of the max, index of min can be retrieved with similar code.

int indexOfMax = Enumerable.Range(0, usageHours.Length).Aggregate(
    (indexOfMax, i) => (usageHours[i] > usageHours[indexOfMax] ? i : indexOfMax)
);

Delegate inside of Aggregate's brackets is executed for each index of array. It gets as parameters "index of maximum value so far found", and current index. It returns "index of maximum value so far found". Obviously in each iteration "index of maximum value so far found" will only change to current index if corresponding element of array is greater than previous maximum.

Denis kotelnikov