8

What's the best algorithm to find the smallest non zero positive value from a fixed number (in this case 3) of values or return 0 if there are no positive questions?

My naive approach is below (in Delphi, but feel free to use whatever you like), but I think there's a more elegant way.

value1Temp := MaxInt;
value2Temp := MaxInt;
value3Temp := MaxInt;

if ( value1T > 0) then
  value1Temp := value1;
if ( value2 > 0) then
  value2Temp := value2;
if ( value3 > 0) then
  value3Temp  := value3;

Result := Min(value1Temp, Min(value2Temp, value3Temp));
if Result = MaxInt then
  Result := 0;

Edit: Sorry added what's needed if there are no positive numbers. I thought I had it in there before, but must have missed it.

Ray
  • 45,695
  • 27
  • 126
  • 169

21 Answers21

3

I'd do a little loop (This is in C, I'm not a Delphi guy):

int maxPositiveValue(int *number, int listSize)
{
    int i, result = 0;

    for(i = 0; i < listSize; i++)
    {
        if(number[i] > 0 && (number[i] < result || result == 0))
            result = number[i];
    }

    return result;
}

The advantage of this code is that it is very readable and can easily be scaled to cope with any length list of values.

UPDATE: I have changed the code in response to the comments I have received below.

This new code is a little more complex but it will now handle:

  1. The case where the list contains no positive integers (returns 0).
  2. The case where the list contains one or more occurences of INT_MAX.
  3. A list of any length.
Adam Pierce
  • 33,531
  • 22
  • 69
  • 89
  • If your list is full of negative numbers, the answer is INT_MAX. If your list is full of INT_MAX's, the answer is still INT_MAX. – isekaijin Dec 18 '08 at 02:15
  • Thanks for the suggestions. I've fixed up the code - except for the case where all the inputs are INT_MAX. I figure that is an extreme edge condition and fixing it would reduce the readability of the code. – Adam Pierce Dec 18 '08 at 03:11
  • Now, if your list is full of INT_MAX's, the answer will be zero, which is worse. – isekaijin Dec 18 '08 at 08:35
  • 1
    This is quite overkill for three small numbers. – csl Dec 18 '08 at 14:14
  • Needs to be edited: result=number[0]; //line 1 and delete the last condition. – Sridhar Iyer Dec 18 '08 at 19:42
  • @Sridhar: What happens when number[0] < 1? – Robert Gamble Dec 19 '08 at 00:06
  • All very good comments. Yes it is overkill for three numbers but on the other hand, it can scale to any amount of numbers. I have updated the code to address @Eduardo's concerns although it is even greater overkill now! – Adam Pierce Dec 19 '08 at 01:18
3

I'd do this:

Result := MaxInt;
if value1 > 0 then Result := min(Result, value1);
if value2 > 0 then Result := min(Result, value2);
if value3 > 0 then Result := min(Result, value3);
if Result = MaxInt then Result := 0;

If you want it in a loop with an arbitrary number of questions, then:

Result := MaxInt;
for I := 1 to N do
   if value[I] > 0 then Result := min(Result, value[I]);
if Result = MaxInt then Result := 0;

If you want the value array to be zero-based, change the for loop to be: 0 to N-1

I think this code makes it very clear exactly what is being done.

Putting the "then" statements on the same line makes the code look cleaner in this simple case, but feel free to indent the "then" statements onto the next line if you feel it's necessary.

lkessler
  • 19,819
  • 36
  • 132
  • 203
3

How about the following function (in Delphi of course):

function LowestPositiveInt(IntArr : array of Integer):Integer;
var
  iX : integer;
  bValid : boolean;
begin
  Result := MaxInt;
  bValid := False;
  for ix := 0 to High(IntArr) do
    if (IntArr[ix] > 0) then
      begin
        bValid := true;
        if (IntArr[iX] < Result) then
          Result := IntArr[ix];
      end;
  if not bValid then
    Result := 0;
end;

then call it like the following:

ShowMessage(IntToStr( LowestPositiveInt([5,2,3,-1,12]) ));

This should return 2. The advantage of this approach is that the array can take any number of items, including integer variables...so using your example above you could say:

Result := LowestPositiveInt( [ Value1, Value2, Value3 ] );

EDIT Updated to handle the LowestPosititiveInt( [ MaxInt, MaxInt, MaxInt ] ) scenario.

skamradt
  • 15,366
  • 2
  • 36
  • 53
2

I don't know Delphi, but here's a quick solution in Ruby (Assume the numbers are in a list)

[1,2,42,-12].delete_if{|n| n <= 0 }.min || 0

Algorithmically, you delete all the negative (or 0) elements, then you find the minimum. If there are no positive elements, [].min returns nil, so the final || 0 gives the requested '0' as an answer.

zenazn
  • 14,295
  • 2
  • 36
  • 26
2

In DELPHI -- if your domain is the integers, and if you can fit your args into longints, and if you can avoid passing the minimum integer ($80000000), then this will give you the result you want without any conditional branching:

function cmMinInt( XX, YY, ZZ : longint ) : longint;
begin
   result := max(0,longint(
   min(longint((XX-1) xor $80000000),
   min(longint((YY-1) xor $80000000),
   longint((ZZ-1) xor $80000000)
   )) xor $80000000)+1);
end;

The technique depends on a reversable lossless remapping of the longint type so that the range we're interested in -- the integers from 1 to MAXINT -- remain in order and occupy the lowest values. Simply toggling the sign bit almost gives what we need, except we don't want 0 included in the lower range. Subtracting 1 first (and adding it back later) fixes that. The xor operation used here widens both operands to int64, which requires an explicit cast back to longint so the min function will produce the correct result. Finally, if the operands are all neg, the minimum will be found in the upper range, and the answer will be neg. In this case we want the answer to be 0, so we simply clip it with the max function.

Here's the same math spread out over multiple statements for easier reading:

function cmMinInt( XX, YY, ZZ : longint ) : longint;
begin
   // swap ordinal coding for range MININT..0 with range 1..MAXINT
   XX := XX-1;             // move region division to between 0 and 1
   XX := XX xor $80000000; // swap regions, preserving ordering
   XX := longint(XX);      // cram back into signed 32-bit
   // similarly with YY and ZZ
   YY := longint((YY-1) xor $80000000);
   ZZ := longint((ZZ-1) xor $80000000);
   // find min of three recoded values
   result := min(XX,min(YY,ZZ));
   // swap ordering back again
   result := result xor $80000000;  // swap regions, preserving ordering
   result := result+1;              // move region division back home
   result := longint(result);       // cram back into signed 32-bit
   // if all three were neg, result at this point is neg -- clip to zero
   result := max(0,result);
end;

-Al.

A. I. Breveleri
  • 325
  • 1
  • 3
1

I agree with Adam. You're not really going to do faster than a linear search algorithmically, if you only need the smallest natural number in a container.

His code should run pretty fast, it would most likely translated to a CMOV in x86, so the if statement inside the for loop won't cost that much anyways.

If you're going to end up wanting all the non-zero numbers in order, then of course it would be much better to sort, and then splice.

Calyth
  • 1,673
  • 3
  • 16
  • 26
  • There's a minor optimization that can be made which is exiting the scanning loop if the value is 1 (as it's the lowest integer above zero so trumps anything else by default). – Troy Howard Dec 18 '08 at 02:09
1

What you want is a selection algorithm if you work with non-fixed number of values.

However, if your code only needs to check three values, you should avoid loops and specific algorithms, and just concentrace on micro-optimizations — specifically, as little branching as possible.

There is some stuff about this in Hacker's Delight, chapter 4, where you can typecast your signed integer to unsigned to halve the number of branches. This is done in the function smallest_v2() in the C-code below:

#include <stdio.h>
#include <limits.h>

int smallest_v1(int a, int b, int c)
{
        int min = INT_MAX;

        min = a>0 && a<min ? a : min;
        min = b>0 && b<min ? b : min;
        min = c>0 && c<min ? c : min;
}

// See Hacker's Delight, chapter 4.
int smallest_v2(int a, int b, int c)
{
        int min = INT_MAX;

        if ( (unsigned) a < min ) min = a;
        if ( (unsigned) b < min ) min = b;
        if ( (unsigned) c < min ) min = c;

        return min;
}

int main()
{
        printf("min v1: %d\n", smallest_v1(-10, 7, 3));
        printf("min v2: %d\n", smallest_v1(-10, 7, 3));
}

Basically, the book says that if you want to check if

1 <= i <= 10

then this is the same as doing an unsigned comparison

(unsigned)(i - 1) <= 9

The book also offers a proof. What you get is better branch prediction in your code. You should make a test program and time it.

csl
  • 10,937
  • 5
  • 57
  • 89
1

Are you looking for aesthetics or speed?

If the latter, I cannot think of a way you could perform this test enough times to be detectable in an application: it just doesn't matter.

Cheers

Richard Haven
  • 1,122
  • 16
  • 31
0

Hard to believe Delphi is still limited to two values in the Min function. :-(

In Python you could set up a function to work with any number of arguments like this:

def PosMin(*numbers):
    try:
        return min(num for num in numbers if num > 0)
    except:
        return 0

Unfortunately the "min" function raises an exception if it ends up with no values (e.g. an empty list or in this case no numbers > 0) so the exception has to be caught. There's a proposal for the next version of python to add a sentinel value and it looks like it's going to be accepted. In that case, the code would have just been

return min (num for num in numbers if num > 0, sentinel=0)

with no need for the exception handling.

alcalde
  • 1,288
  • 13
  • 10
  • *Hard to believe Delphi is still limited to two values in the Min function.* Use MinValue then – David Heffernan Aug 07 '13 at 06:52
  • 1
    @DavidHeffernan There shouldn't be three different Min functions. It's quite possible to implement a Min function with an arbitrary number of variables. It's one of the many dusty corners of Delphi's standard library where functions have not been updated/enhanced to incorporate modern features of the language. – alcalde Aug 07 '13 at 20:08
0
function MinPositive(const AIntegers: array of Integer): Integer;
var
  LoopI: Integer;
  LFirstPositivePos: Integer;
begin
  Result := 0;
  LFirstPositivePos := MaxInt;
  for LoopI := Low(AIntegers) to High(AIntegers) do
  begin
    if (AIntegers[LoopI] > 0) then
    begin
      Result := AIntegers[LoopI];
      LFirstPositivePos := LoopI;
      Break;
    end;
  end;

  for LoopI := LFirstPositivePos to High(AIntegers) do
  begin
    if (AIntegers[LoopI] > 0) and (AIntegers[LoopI] < Result) then
    begin
      Result := AIntegers[LoopI];
    end;
  end;
end;

function MinPositive3(const I1, I2, I3: Integer): Integer;
begin
  Result := MinPositive([I1, I2, I3]);
end;
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
0
Result := Min(IfThen(Value1 > 0, Value1, MAXINT), 
              Min(IfThen(Value2 > 0, Value2, MAXINT),
                  IfThen(Value3 > 0, Value3, MAXINT)));

A loop won't work if the inputs aren't a list/array, per the question.

It's not clear from the question what the function should do if none of the three are positive and non-zero.

Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
  • Does that work? It seems I would still get zero if Value1 was 5, and Value2 and Value3 were 0). – Ray Dec 18 '08 at 02:09
  • Umm.. so put them into an array and then use the looping algorithm. ;) – Troy Howard Dec 18 '08 at 02:14
  • Sorry, I thought I had in my question that I wanted zero if there were no positive numbers. Edited question. – Ray Dec 18 '08 at 03:37
0

This works no matter what the type of the array's elements is

template <typename T>
T min_pos(T* a, int n)
{
    int i /* = 0 */ /* Edit: commented this out */;

    // Find the first positive element in the array
    for (i = 0; i < n; ++i)
        if (a[i] > 0)
            break;

    // If no positive element was found, return 0
    if (i == n)
        return 0;

    T res = a[i];

    // Search the rest of the array for an element
    // that is positive yet smaller than res
    for (++i; i < n; ++i)
    {
        if ((a[i] > 0) && (a[i] < res))
            res = a[i];
    }

    return res;
}
isekaijin
  • 19,076
  • 18
  • 85
  • 153
  • It's more common to have operator< defined than operator> for user-defined types. Your code requires the type T to have a conversion from int. Also, note that this was a Delphi question, not C++. – Rob Kennedy Dec 18 '08 at 03:24
  • I'm not really worried about the language as it's pretty easy to translate these simple algorithms. – Ray Dec 18 '08 at 03:31
  • Rob, my code doesn't require the type to have a conversion from int. It also works for doubles. – isekaijin Dec 18 '08 at 08:37
0

This is C#

public int GetSmallestPositive(IList<int> pValues)
{
    if(pValues == null)
    {
        throw new ArgumentNullException("pValues");
    }
    int _negNumCount = 0;
    int _smallest = int.MaxValue;
    for(int i = 0; i < pValues.Count; ++i)
    {
        if(pValues[i] < _smallest)
        {
            if(pValues[i] <= 0)
            {
                ++_negNumCount;
                continue;
            }
            _smallest = pValues[i];
            if(_smallest == 1)
            {
                return 1;
            }
        }
    }
    return (_negNumCount == pValues.Count) ? 0 : _smallest;
}

In this case, and in your example, I'm using ints, so 1 is the smallest non-zero number. As long as you put your ints into a list, it will work for as many values as you want.

Edit: Fixed to return 0 if the list is full of negative numbers. Throw exception if pValues is null.

Rex Morgan
  • 2,979
  • 2
  • 21
  • 32
  • If your list is full of negative numbers, the answer is MaxInt. If your list is full of MaxInt's, the answer is still MaxInt. – isekaijin Dec 18 '08 at 02:13
  • If the list is full of MaxInts, the code *should* return MaxInt, as it's the smallest value in the list. – Troy Howard Dec 18 '08 at 02:17
  • Of course, but if the list is full of nonpositive numbers, the answer should be a nonpositive number or an exception, so the user of the function knows that the list has no positive numbers. – isekaijin Dec 18 '08 at 03:02
  • @Eduardo: It's unclear what should be returned if the list is full of negative numbers. The return type could be changed to a nullable int and null could be returned if there are no values > 0 found in the list. Or we could just return 0, as that would be an indication that something went wrong. – Rex Morgan Dec 18 '08 at 03:04
  • Whatever it's supposed to do for a list of negative numbers, it should NOT return something that looks like a valid result. Return an item from the list, or return zero, or throw an exception, but don't return a positive value. – Rob Kennedy Dec 18 '08 at 03:27
0
//return the smallest non-zero positive number, or null.
//relying on Min or Max is considered cheating.
public static int? smallestNonZeroPositiveNumberOfThree(
    int val1, int val2, int val3)
{
    //we have no guarantee that any of the 3 inputs will be positive
    int? result = null;
    if (val1 > 0) 
    {
        result = val1; 
        if (val2 > 0 && val2 < result) { result = val2; }
        if (val3 > 0 && val3 < result) { result = val3; }
    }
    else if (val2 > 0)
    {
        result = val2; 
        if (val3 > 0 && val3 < result) { result = val3; }
    }
    else if (val3 > 0)
    {
        result = val3; 
    }
    return result;
}
Steven A. Lowe
  • 60,273
  • 18
  • 132
  • 202
0

A c# version that covers all the bases (i think):

public int GetMinimumPositiveValue(IEnumerable<int> values)
{
    int result = int.MaxValue;
    bool hasPositiveValue = false;

    foreach (int value in values)
    {
        if(value == 1) { return value; } 

        if(value > 0)
        {
            hasPositiveValue = true;

            if(value < result)
            {
                result = value;
            }
        }
    }

    return hasPositiveValue ? result : 0;
}
Troy Howard
  • 2,612
  • 21
  • 25
0

Here's what I came up with after thinking about it a bit more

Result := 0;
if value1 > 0 then
  Result := value1;
if (value2 > 0) and ((Result = 0) or (value2 < Result)) then
  Result := value2;
if (value3 > 0) and ((Result = 0) or (value3 < Result)) then
  Result := value3;

Granted if you have a list, the more generic algorithms are better.

Ray
  • 45,695
  • 27
  • 126
  • 169
0

Here's a C version riffing off of the solution in the question post, but fixes the case where all of the values are MaxInt...

int foo(int value1, int value2, int value3)
{
    int value1Temp, value2Temp, value3Temp, tempMax;
    value1Temp = max(value1, 0);
    value2Temp = max(value2, 0);
    value3Temp = max(value3, 0);
    tempMax = value1Temp | value2Temp | value3Temp;
    if (value1Temp == 0) { value1Temp = tempMax; }
    if (value2Temp == 0) { value2Temp = tempMax; }
    if (value3Temp == 0) { value3Temp = tempMax; }
    return min(value1Temp, min(value2Temp, value3Temp));
}

It's also possible to do this in a branch-free way, since min and max can be implemented as branch-free operations:

int min(int x, int y)
{
    return y + ((x - y) & -(x < y));
}

int max(int x, int y)
{
    return x - ((x - y) & -(x < y));
}

int foo(int value1, int value2, int value3)
{
    int value1Temp, value2Temp, value3Temp, tempMax, mask;
    value1Temp = max(value1, 0);
    value2Temp = max(value2, 0);
    value3Temp = max(value3, 0);
    tempMax = value1Temp | value2Temp | value3Temp;
    mask = -(value1Temp > 0);
    value1Temp = (value1Temp & mask) | (tempMax & ~mask);
    mask = -(value2Temp > 0);
    value2Temp = (value2Temp & mask) | (tempMax & ~mask);
    mask = -(value3Temp > 0);
    value3Temp = (value3Temp & mask) | (tempMax & ~mask);
    return min(value1Temp, min(value2Temp, value3Temp));
}

For more background on why you'd ever want to do this, see: Is "If" Expensive? and Bit Twiddling Hacks.

Edit: Clobbered my earlier attempt at a branch-free solution, which didn't actually work. Added a new branch-free solution which should work.

Community
  • 1
  • 1
Parappa
  • 7,566
  • 3
  • 34
  • 38
0

A slight improvement on Jason's suggestion that correctly handles empty collections and collections containing only negative values:

values.Min(r => r > 0 ? r : (int?)null) ?? 0
Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56
0

In Haskell, as usual, its easiest to solve the general problem and then declare a special case.

foo xs = let xs1 = filter (>0) xs in if null xs1 then 0 else minimum xs1

foo3 x1 x2 x3 = foo [x1, x2, x3]
Paul Johnson
  • 17,438
  • 3
  • 42
  • 59
-1
  • Go through the values of the list, discarding them until you find a positive value, set min-value to it
  • Go through the values in the rest of the list
    • If 0 < current-value < min-value, set min-value to current-value
  • return min-value
Svante
  • 50,694
  • 11
  • 78
  • 122
  • This doesn't work if the first value in the list is not positive. – isekaijin Dec 18 '08 at 02:09
  • This was the solution I came up with between posting the question and now, except to actually make it work, Set min-value to zero first and for the first value don't check if it's less than min-value. – Ray Dec 18 '08 at 02:12
-1

I see way too many lines of codes for those trying to solve the general problem in C#. If values is an IEnumerable<int> then

values.Select(v => (int?)v)
      .Where(v => v > 0)
      .Min() ?? 0;

returns the smallest positive value in values if one exists otherwise it returns 0. Here, I'm exploiting the fact that Enumerable.Min(IEnumerable<int?>) will return null if the sequence is empty. So, we filter out the non-positive values, and then find the minimum. If all the values are non-positive, we obtain zero as desired and otherwise find the minimum of the positive values.

jason
  • 236,483
  • 35
  • 423
  • 525
  • There are two different cases your program can't identify: * All elements are equal to Int32.MaxValue * All elements are nonpositive – isekaijin Dec 18 '08 at 03:26
  • This is true, however not necessarily a problem as long as one is aware of the limitation. At least the code is a lot cleaner than some of the other answers. – Ben Childs Dec 18 '08 at 04:19
  • @Eduardo León: Going way back in history here, but I've addressed your concern. – jason Jun 23 '13 at 14:49