【问题标题】:Find the Minimum Positive Value找到最小正值
【发布时间】:2010-09-27 11:02:39
【问题描述】:

从固定数量(在本例中为 3)的值中找到最小的非零正值或在没有正问题时返回 0 的最佳算法是什么?

我的幼稚方法如下(在 Delphi 中,但可以随意使用任何你喜欢的方法),但我认为还有一种更优雅的方法。

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;

编辑:如果没有正数,抱歉添加了所需的内容。我以为我以前有它,但一定错过了。

【问题讨论】:

  • 当三个元素都是 MaxInt 时,你的代码不起作用。
  • 好点。我可以检查这三个值是否为零,如果为零则提前返回(但我知道它们不会是 MaxInt)。
  • 如果三个值都是 MaxInt,MaxInt 是列表中最小的非零正值...对吗?
  • 是的,没错。但是上面的代码会返回 0,而不是 MaxInt。

标签: algorithm delphi optimization


【解决方案1】:

我会做一个小循环(这是在 C 中,我不是 Delphi 人):

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;
}

此代码的优点是可读性强,可以轻松缩放以处理任何长度的值列表。

更新:我已更改代码以响应我在下面收到的 cmets。

这个新代码有点复杂,但现在可以处理:

  1. 列表不包含正整数的情况(返回 0)。
  2. 列表包含一次或多次出现的 INT_MAX 的情况。
  3. 任意长度的列表。

【讨论】:

  • 如果您的列表中充满了负数,则答案是 INT_MAX。如果您的列表中充满了 INT_MAX,那么答案仍然是 INT_MAX。
  • 感谢您的建议。我已经修复了代码——除了所有输入都是 INT_MAX 的情况。我认为这是一个极端的边缘条件,修复它会降低代码的可读性。
  • 现在,如果您的列表中充满了 INT_MAX,那么答案将是零,这会更糟。
  • 这对于三个小数字来说是相当大的。
  • 需要编辑:result=number[0]; //第1行并删除最后一个条件。
【解决方案2】:

我同意亚当的观点。如果您只需要容器中的最小自然数,您实际上不会比线性搜索算法更快。

他的代码应该运行得非常快,它很可能会在 x86 中转换为 CMOV,因此 for 循环中的 if 语句无论如何都不会花费那么多。

如果您最终想要所有非零数字按顺序排列,那么当然最好先排序,然后拼接。

【讨论】:

  • 如果值为 1,则可以进行一个小的优化,即退出扫描循环(因为它是大于零的最小整数,因此默认情况下优于其他任何值)。
【解决方案3】:
Result := Min(IfThen(Value1 > 0, Value1, MAXINT), 
              Min(IfThen(Value2 > 0, Value2, MAXINT),
                  IfThen(Value3 > 0, Value3, MAXINT)));

根据问题,如果输入不是列表/数组,则循环将不起作用。

如果这三个都不是正数和非零值,该函数应该做什么,这个问题并不清楚。

【讨论】:

  • 这行得通吗?如果 Value1 为 5,Value2 和 Value3 为 0,我似乎仍然会得到零。
  • Umm.. 所以把它们放到一个数组中,然后使用循环算法。 ;)
  • 对不起,我想我的问题是如果没有正数,我想要零。已编辑的问题。
【解决方案4】:
  • 遍历列表的值,丢弃它们直到找到一个正值,然后设置最小值
  • 遍历列表其余部分中的值
    • 如果 0
  • 返回最小值

【讨论】:

  • 如果列表中的第一个值不是正数,这将不起作用。
  • 这是我在发布问题和现在之间提出的解决方案,除了让它真正起作用,首先将最小值设置为零,对于第一个值不要检查它是否小于最小值。
【解决方案5】:

无论数组元素的类型是什么,这都有效

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;
}

【讨论】:

  • 对于用户定义的类型,操作符更常见。您的代码要求类型 T 具有从 int 的转换。另外,请注意这是一个 Delphi 问题,而不是 C++。
  • 我并不担心语言,因为翻译这些简单的算法非常容易。
  • Rob,我的代码不需要从 int 转换类型。它也适用于双打。
【解决方案6】:

这是 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;
}

在这种情况下,在您的示例中,我使用的是整数,因此 1 是最小的非零数。只要您将 int 放入一个列表中,它就可以用于任意数量的值。

编辑:如果列表中满是负数,则固定返回 0。如果 pValues 为 null,则抛出异常。

【讨论】:

  • 如果你的列表中满是负数,答案就是 MaxInt。如果您的列表中充满了 MaxInt,那么答案仍然是 MaxInt。
  • 如果列表中满是 MaxInt,代码应该返回 MaxInt,因为它是列表中的最小值。
  • 当然可以,但是如果列表中充满了非正数,那么答案应该是非正数或者异常,所以函数的用户知道列表中没有正数。
  • @Eduardo:如果列表中满是负数,还不清楚应该返回什么。返回类型可以更改为可为 null 的 int,如果在列表中找不到 > 0 的值,则可以返回 null。或者我们可以只返回 0,因为这表明出了问题。
  • 无论它应该对负数列表做什么,它都不应该返回看起来像有效结果的东西。从列表中返回一个项目,或者返回零,或者抛出异常,但不返回正值。
【解决方案7】:
//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;
}

【讨论】:

    【解决方案8】:

    一个涵盖所有基础的 c# 版本(我认为):

    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;
    }
    

    【讨论】:

      【解决方案9】:

      这是我想了想之后想出来的

      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;
      

      如果你有一个列表,那么更通用的算法会更好。

      【讨论】:

      • 这非常简洁,完全适合短名单。我会给它+1!
      【解决方案10】:

      对于那些试图解决 C# 中的一般问题的人来说,我看到的代码行太多了。如果 values 是 IEnumerable&lt;int&gt; 那么

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

      如果存在则返回values 中最小的正值,否则返回0。在这里,我利用Enumerable.Min(IEnumerable&lt;int?&gt;) 将在序列为空时返回null 的事实。因此,我们过滤掉非正值,然后找到最小值。如果所有的值都是非正的,我们会根据需要获得零,否则找到正值的最小值。

      【讨论】:

      • 您的程序无法识别两种不同的情况: * 所有元素都等于 Int32.MaxValue * 所有元素都是非正数
      • 这是对的,但只要知道限制就不一定是问题。至少代码比其他一些答案干净得多。
      • @Eduardo León:这里可以追溯到历史,但我已经解决了你的问题。
      【解决方案11】:

      我会这样做:

      结果 := MaxInt;
      如果 value1 > 0 那么 Result := min(Result, value1);
      如果 value2 > 0 那么 Result := min(Result, value2);
      如果 value3 > 0 那么 Result := min(Result, value3);
      如果 Result = MaxInt 则 Result := 0;

      如果您希望它包含任意数量的问题,那么:

      结果 := MaxInt;
      对于 I := 1 到 N 做
      如果 value[I] > 0 那么 Result := min(Result, value[I]);
      如果 Result = MaxInt 则 Result := 0;

      如果您希望值数组从零开始,请将 for 循环更改为:0 到 N-1

      我认为这段代码清楚地表明了正在做什么。

      将“then”语句放在同一行会使代码在这个简单的情况下看起来更简洁,但如果您认为有必要,可以将“then”语句缩进到下一行。

      【讨论】:

        【解决方案12】:

        这是问题帖子中解决方案的 C 版本,但修复了所有值都是 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));
        }
        

        也可以通过无分支的方式执行此操作,因为 min 和 max 可以实现为无分支操作:

        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));
        }
        

        有关您为什么要这样做的更多背景信息,请参阅:Is "If" Expensive?Bit Twiddling Hacks

        编辑:破坏了我之前对无分支解决方案的尝试,但实际上并没有奏效。添加了一个新的无分支解决方案,它应该可以工作。

        【讨论】:

          【解决方案13】:

          Jason 提出的正确处理空集合和仅包含负值的集合的建议略有改进:

          values.Min(r => r > 0 ? r : (int?)null) ?? 0
          

          【讨论】:

            【解决方案14】:

            下面的函数怎么样(当然是在 Delphi 中):

            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;
            

            然后像下面这样调用它:

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

            这应该返回 2。这种方法的优点是数组可以包含任意数量的项目,包括整数变量...所以使用上面的示例,您可以说:

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

            EDIT 已更新以处理 LowestPosititiveInt( [ MaxInt, MaxInt, MaxInt ] ) 场景。

            【讨论】:

              【解决方案15】:

              如果您使用非固定数量的值,您需要的是 selection algorithm

              但是,如果您的代码只需要检查三个值,则应避免循环和特定算法,而只专注于微优化——特别是尽可能少的分支。

              Hacker's Delight 第 4 章中有一些关于此的内容,您可以在其中 将您的有符号整数类型转换为无符号以将分支数减半。这是 在下面的 C 代码中的函数 minimum_v2() 中完成:

              #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));
              }
              

              基本上,书上说如果你想检查是否

              1 <= i <= 10
              

              那么这和做一个无符号比较是一样的

              (unsigned)(i - 1) <= 9
              

              这本书也提供了一个证明。你得到的是代码中更好的分支预测。你应该制定一个测试程序并计时。

              【讨论】:

                【解决方案16】:

                我不知道 Delphi,但这里有一个 Ruby 中的快速解决方案(假设数字在列表中)

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

                从算法上讲,您删除所有负数(或 0)元素,然后找到最小值。如果没有正元素,[].min 返回nil,所以最后的|| 0 给出请求的“0”作为答案。

                【讨论】:

                • 这是 Ruby 中的一行代码。不幸的是,它不容易转换成 Delphi。
                【解决方案17】:

                您是追求美观还是追求速度?

                如果是后者,我想不出一种方法可以让您执行足够多次的测试以在应用程序中检测到:这没关系。

                干杯

                【讨论】:

                  【解决方案18】:

                  在 DELPHI 中——如果您的域是整数,并且您可以将您的 args 放入 longints 中,并且如果您可以避免传递最小整数 ($80000000),那么这将为您提供您想要的结果,而无需任何条件分支:

                  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;
                  

                  该技术依赖于 longint 类型的可逆无损重新映射,以便我们感兴趣的范围(从 1 到 MAXINT 的整数)保持有序并占据最低值。简单地切换符号位几乎可以满足我们的需要,除了我们不希望 0 包含在较低范围内。首先减去 1(稍后再添加)可以解决此问题。此处使用的 xor 操作将两个操作数都扩展为 int64,这需要显式转换回 longint,因此 min 函数将产生正确的结果。最后,如果操作数都是 neg,则最小值将在上限范围内找到,答案将为 neg。在这种情况下,我们希望答案为 0,因此我们只需使用 max 函数对其进行裁剪。

                  为了便于阅读,下面是相同的数学运算分布在多个语句中:

                  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;
                  

                  -阿尔。

                  【讨论】:

                  • 我想这就是当人们要求“最好”但没有说“就某事而言最好”时得到的结果。
                  【解决方案19】:

                  在 Haskell 中,像往常一样,最容易解决一般问题,然后声明一个特殊情况。

                  foo xs = let xs1 = filter (>0) xs in if null xs1 then 0 else minimum xs1
                  
                  foo3 x1 x2 x3 = foo [x1, x2, x3]
                  

                  【讨论】:

                    【解决方案20】:

                    很难相信 Delphi 仍然限制在 Min 函数中的两个值。 :-(

                    在 Python 中,您可以设置一个函数来处理任意数量的参数,如下所示:

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

                    不幸的是,如果“min”函数最终没有值(例如,一个空列表或在这种情况下没有数字> 0),则会引发异常,因此必须捕获异常。有一个关于 python 的下一个版本添加一个哨兵值的建议,看起来它会被接受。在那种情况下,代码应该是

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

                    不需要异常处理。

                    【讨论】:

                    • 很难相信 Delphi 仍然限制在 Min 函数中的两个值。 然后使用 MinValue
                    • @DavidHeffernan 不应该有三个不同的 Min 函数。使用任意数量的变量来实现 Min 函数是完全可能的。它是 Delphi 标准库中众多尘土飞扬的角落之一,其中的功能尚未更新/增强以整合该语言的现代特性。
                    【解决方案21】:
                    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;
                    

                    【讨论】:

                      猜你喜欢
                      • 2020-10-17
                      • 1970-01-01
                      • 2015-02-01
                      • 2012-06-21
                      • 1970-01-01
                      • 2015-10-28
                      • 2017-11-18
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多