2

I need ideas for a simple, lightweight way to get the max and min values of an array of doubles. The trick is I only need the max and min between two indexes in the array.

The built-in arrayOfDoubles.Max() and arrayOfDoubles.Min() will not work, because they check the entire array.

This code will be running constantly and needs to be somewhat efficient. That said, simplicity and readability are more important than speed alone.

Here's one way to get the max and min between two indexes:

double[] array = new double[8] { 3, 1, 15, 5, 11, 9, 13, 7 };

int startIndex = 3;
int endIndex = 6;

// Get max and min between these two indexes in the array
double max = GetMax(array, startIndex, endIndex);
double min = GetMin(array, startIndex, endIndex);

Console.WriteLine("Max = " + max + ", Min = " + min);

Here is the code for GetMax, and GetMin would be very similar:

private static double GetMax(double[] array, int startIndex, int endIndex)
{
    double max = double.MinValue;

    for (int i = startIndex; i <= endIndex; i++)
    {
        // Increase max with each bigger value
        max = Math.Max(max, array[i]);
    }

    return max;
}

And the output: Max = 13, Min = 5

The Question: What other ways could I get the same result that might be simpler and wouldn't require too much overhead?

AndrewRalon
  • 496
  • 1
  • 9
  • 24
  • There really isn't anything simpler than this. – cadrell0 Jul 16 '14 at 15:50
  • 4
    Only thing might be to do both min and max in the same loop. You could manually inline the Math.Min and Math.Max calls, but those will probably be inlined automatically anyway. – Chris Jul 16 '14 at 15:51
  • http://stackoverflow.com/questions/5478877/math-max-vs-inline-if-what-are-the-differences – Chris Jul 16 '14 at 15:55
  • 1
    How often will the array contents change? Are all elements changing between each iteration, or will only some elemnts change? What I'm getting at is that there may be better structures to store your data.. – Mike Dinescu Jul 16 '14 at 15:55
  • By the way, how large of an array are we talking about here? – Chris Jul 16 '14 at 15:56
  • @MikyDinescu The array elements will all be changing constantly. – AndrewRalon Jul 16 '14 at 15:57
  • @Chris The array length will always be 8. – AndrewRalon Jul 16 '14 at 15:57
  • How often will you be running this? – Chris Jul 16 '14 at 15:59
  • If the items are all changing constantly, then you have to do this in a single iteration of the list. Otherwise there's the potential of `GetMin` returning a value that's larger than what `GetMax` returns. – Jim Mischel Jul 16 '14 at 16:29

5 Answers5

7
var list = array.Skip(startIndex).Take(endIndex - startIndex + 1).ToList();
var min = list.Min();
var max = list.Max();
EZI
  • 15,209
  • 2
  • 27
  • 33
  • ToList() is unnecessary. – slippyr4 Jul 16 '14 at 16:02
  • 1
    @slippyr4 It is necessary to iterate over the array once (Two iterations over sublist). – EZI Jul 16 '14 at 16:03
  • 1
    No it's not. The second iteration causes a requery. But for an 8 element array the requery isn't going to be a world apart from instantiating a List object. So, again, ToList() is not necessary. – slippyr4 Jul 16 '14 at 16:10
  • 3
    @slippyr4 `The second iteration causes a requery`, Only on the the sublist (when using ToList()). So I disagree, but anyway, at least, It is not worse than not using it. – EZI Jul 16 '14 at 16:12
  • Given that not using ToList() produces the same results, I'm sticking with it being unnecessary. – slippyr4 Jul 16 '14 at 22:45
2

You can do this by scanning the array once. This is the standard min/max tracking algorithm that is asked in a bazillion interview questions, so readability shouldn't be an issue.

void GetMinMax(double[] array, int start, int end, out double min, out double max)
{
    min = Double.MaxValue;
    max = Double.MinValue;

    // TODO: error checks for out-of-bounds, null, etc...
    for (int i = start; i <= end; ++i)
    {
        if (array[i] > max) max = array[i];
        if (array[i] < min) min = array[i];
    }
}

Edit:

Using Usman Zafar's answer to simplify and remove the need for several error checks:

void GetMinMax(IList<double> list, out double min, out double max)
{
    // TODO: null/empty check for list.

    min = Double.MaxValue;
    max = Double.MinValue;

    for (int i = 0; i < list.Count; ++i)
    {
        if (list[i] > max) max = list[i];
        if (list[i] < min) min = list[i];
    }
}

Now call with any IList<double>, or in your case: new ArraySegment<double>(array, 3, 4).

Community
  • 1
  • 1
Steve Guidi
  • 19,700
  • 9
  • 74
  • 90
2

You can also do:

var segment = new ArraySegment<double>(array, startIndex, endIndex - startIndex + 1);
var min = segment.Min();
var max = segment.Max();
Usman Zafar
  • 1,919
  • 1
  • 15
  • 11
  • +1 for ArraySegment: This `struct` effectively encapsulates all sub-array bounds checking, while also implementing the `IList` interface! – Steve Guidi Jul 16 '14 at 16:13
2

It should be obvious that finding the minimum and maximum elements in an unsorted array requires an O(n) algorithm because no matter how you do it you do have to examine each element, at least once. So the only optimizations you can hope for are implementation specific and can only bring you gains in terms of the constants.

There is however a clever trick you can employ for finding both minimum and maximum in the array using only 3*[n/2] comparisons. While this is not an asymptotic improvement, it is an improvement of n/2 compared to the typical 2*n comparison that are needed in the straight forward algorithm. So, if you expect to be performing lots of the min/max computations then it may be worth considering it as an option.

The way you do it is like this:

  • maintain both minimum and maximum elements so far in two variables
  • at each iteration:
    • compare the next two elements between each other to determine which one is larger
    • compare the larger element with the maximum (and swap if necessary)
    • compare the smaller element with the minimum (and swap if necessary)

You will be executing n/2 iterations of that loop, and 3 comparisons for each iteration for a total of 3*[n/2] operations.

Here's an implementation:

void GetMinMax(double[] array, int start, int end, out double min, out double max)
{
    min = array[start];
    max = array[start];

    if((end - start) % 2 == 0)               // if there's an odd number of elements
       start = start + 1;                    //   skip the first one

    for (int i = start + 1; i <= end; i += 2)
    {
        if(array[i] > array[i-1])
        {
            if(array[i] > max) max = array[i];
            if(array[i - 1] < min) min = array[i - 1];
        } else {
            if(array[i - 1] > max) max = array[i - 1];
            if(array[i] < min) min = array[i];
        }
    }
}
Mike Dinescu
  • 54,171
  • 16
  • 118
  • 151
  • I love how thorough and efficient this is! My criteria of simplicity and readability, however, leads me to another answer. – AndrewRalon Jul 16 '14 at 19:23
  • @andrewralon - glad you appreciated the answer. I definitely wouldn't recommend implementing this routinely since the savings usually don't justify the more complicated code. The LINQ solution definitely wins in the simplicity/expressiveness department but offers no efficiency improvements. – Mike Dinescu Jul 16 '14 at 22:06
-1

using the sweet drug that is LINQ:

        var res = array.Skip(startIndex).Take(1 + endIndex - startIndex );
        var min = res.Min();
        var max = res.Max();
slippyr4
  • 862
  • 7
  • 18
  • 4
    You forget to take the last element. Compare your code with the expected values in question. – EZI Jul 16 '14 at 16:16