比较三个数字的简单方法


11

我有一些代码,其中包含一系列if可以正常工作的,但是感觉很混乱。基本上,我想选择三个整数中最大的一个,并设置一个状态标志来说明选择了哪个。我当前的代码如下所示:

a = countAs();
b = countBs();
c = countCs();

if (a > b && a > c)
    status = MOSTLY_A;
else if (b > a && b > c)
    status = MOSTLY_B;
else if (c > a && c > b)
    status = MOSTLY_C;
else
    status = DONT_KNOW;

这种模式会发生几次,并且变量名较长,从视觉上确认每一个if都是正确的会有些困难。我觉得可能会有更好,更清晰的方法来执行此操作;有人可以建议什么吗?


有一些潜在的重复项,但它们与这个问题不太吻合。

在建议的副本中: 检查多个条件的方法? 所有建议的解决方案似乎都与原始代码一样笨拙,因此无法提供更好的解决方案。

这篇文章优雅的处理if(if else)else的方法仅涉及嵌套级别和不对称性,这不是这里的问题。


3
对我来说,唯一的问题是代码重复。您在这里拥有的内容非常清晰易读,为什么要更改它?只需将其分解为一个接受a,b,c并返回状态的函数即可。如果您感觉更好,请在其上放一个“内联”。没有宏,没有复杂性,只有好的旧函数提取。如果以后需要使用它,我将感谢您提供清晰的代码。
J Trana 2015年



请注意,您的#defines命名不正确。考虑a = 40,b = 30,c =30。结果为MOSTLY_A。但是大多数事情实际上不是A,而是B或C。您可能想尝试MORE_A(尽管仍然模棱两可-比B多A,比C多A,还是比B和C多A?),或者A_LARGEST, MAX_IS_A,...?(这也建议将max(a,max(b,c))作为问题的答案)。
Tony 2015年

Answers:


12

分解逻辑,尽早返回

正如注释中所建议的那样,将逻辑简单地包装在函数中并用早退出就足够了,return以简化很多事情。另外,您可以通过将测试委派给另一个功能来分解一些功能。更具体地说:

bool mostly(max,u,v) {
   return max > u && max > v;
}

status_t strictly_max_3(a,b,c)
{
  if mostly(a,b,c) return MOSTLY_A;
  if mostly(b,a,c) return MOSTLY_B;
  if mostly(c,a,b) return MOSTLY_C;
  return DONT_KNOW;
}

这比我以前的尝试要短:

status_t index_of_max_3(a,b,c)
{
  if (a > b) {
    if (a == c)
      return DONT_KNOW;
    if (a > c)
      return MOSTLY_A;
    else
      return MOSTLY_C;
  } else {
    if (a == b)
      return DONT_KNOW;
    if (b > c)
      return MOSTLY_B;
    else
      return MOSTLY_C;
  }
}

上面的内容比较冗长,但是易于理解,恕我直言,并且不会多次重新计算比较。

视觉确认

您的回答中,您说:

我的问题主要是视觉确认所有比较都使用相同的变量

...此外,在您的问题中,您说:

这种模式会发生几次,并且如果变量名较长,则在视觉上确认每个if是否正确都有些困难。

我可能无法理解您要实现的目标:是否要在需要的任何地方复制粘贴该模式?使用上述功能,您只需捕获一次模式ab然后c根据需要检查一次所有比较使用的所有内容。这样,您在调用函数时就不必担心了。当然,也许在实践中,您的问题比您所描述的问题要复杂一些:如果是这样,请尽可能添加一些详细信息。


1
我不理解您对DONT_KNOW的评论,如果c最小而a和b相同怎么办?该算法将返回b为最大,而a与b相同。
Pieter B

@PieterB我很担心,假设我们返回a MOSTLY_AMOSTLY_Ccase a == c和都没关系a > b。这是固定的。谢谢。
coredump

@DocBrown当然,大多数好处来自提前退出行为。
coredump

1
+1,现在确实是对OP原始代码的改进。
布朗

9

TL:DR; 您的代码已经正确并且“干净”。

我看到很多人都在四处寻找答案,但是每个人都在树林中迷失了森林。让我们做完整的计算机科学和数学分析以完全理解这个问题。

首先,我们注意到我们有3个变量,每个变量具有3个状态:<,=或>。排列总数为3 ^ 3 = 27个状态,我将为每个状态分配一个唯一的数字,表示为P#。这个P#数字是阶乘数系统

列举所有的排列:

a ? b | a ? c | b ? c |P#| State
------+-------+-------+--+------------
a < b | a < c | b < c | 0| C
a = b | a < c | b < c | 1| C
a > b | a < c | b < c | 2| C
a < b | a = c | b < c | 3| impossible a<b b<a
a = b | a = c | b < c | 4| impossible a<a
a > b | a = c | b < c | 5| A=C > B
a < b | a > c | b < c | 6| impossible a<c a>c
a = b | a > c | b < c | 7| impossible a<c a>c
a > b | a > c | b < c | 8| A
a < b | a < c | b = c | 9| B=C > A
a = b | a < c | b = c |10| impossible a<a
a > b | a < c | b = c |11| impossible a<c a>c
a < b | a = c | b = c |12| impossible a<a
a = b | a = c | b = c |13| A=B=C
a > b | a = c | b = c |14| impossible a>a
a < b | a > c | b = c |15| impossible a<c a>c
a = b | a > c | b = c |16| impossible a>a
a > b | a > c | b = c |17| A
a < b | a < c | b > c |18| B
a = b | a < c | b > c |19| impossible b<c b>c
a > b | a < c | b > c |20| impossible a<c a>c
a < b | a = c | b > c |21| B
a = b | a = c | b > c |22| impossible a>a
a > b | a = c | b > c |23| impossible c>b b>c
a < b | a > c | b > c |24| B
a = b | a > c | b > c |25| A=B > C
a > b | a > c | b > c |26| A

通过检查,我们看到:

  • 3个州,其中A是最大值,
  • 3个州,其中B是最大值,
  • 3个州,其中C是最大值,并且
  • 4个州,其中A = B或B = C。

让我们编写一个程序(请参阅脚注)以使用A,B和C的值枚举所有这些排列。通过P#进行稳定排序:

a ?? b | a ?? c | b ?? c |P#| State
1 <  2 | 1 <  3 | 2 <  3 | 0| C
1 == 1 | 1 <  2 | 1 <  2 | 1| C
1 == 1 | 1 <  3 | 1 <  3 | 1| C
2 == 2 | 2 <  3 | 2 <  3 | 1| C
2  > 1 | 2 <  3 | 1 <  3 | 2| C
2  > 1 | 2 == 2 | 1 <  2 | 5| ??
3  > 1 | 3 == 3 | 1 <  3 | 5| ??
3  > 2 | 3 == 3 | 2 <  3 | 5| ??
3  > 1 | 3  > 2 | 1 <  2 | 8| A
1 <  2 | 1 <  2 | 2 == 2 | 9| ??
1 <  3 | 1 <  3 | 3 == 3 | 9| ??
2 <  3 | 2 <  3 | 3 == 3 | 9| ??
1 == 1 | 1 == 1 | 1 == 1 |13| ??
2 == 2 | 2 == 2 | 2 == 2 |13| ??
3 == 3 | 3 == 3 | 3 == 3 |13| ??
2  > 1 | 2  > 1 | 1 == 1 |17| A
3  > 1 | 3  > 1 | 1 == 1 |17| A
3  > 2 | 3  > 2 | 2 == 2 |17| A
1 <  3 | 1 <  2 | 3  > 2 |18| B
1 <  2 | 1 == 1 | 2  > 1 |21| B
1 <  3 | 1 == 1 | 3  > 1 |21| B
2 <  3 | 2 == 2 | 3  > 2 |21| B
2 <  3 | 2  > 1 | 3  > 1 |24| B
2 == 2 | 2  > 1 | 2  > 1 |25| ??
3 == 3 | 3  > 1 | 3  > 1 |25| ??
3 == 3 | 3  > 2 | 3  > 2 |25| ??
3  > 2 | 3  > 1 | 2  > 1 |26| A

如果您想知道我如何知道哪些P#状态是不可能的,那么现在您知道了。:-)

确定顺序的最小比较数为:

Log2(27)= Log(27)/ Log(2)=〜4.75 = 5个比较

即coredump给出了正确的5个最小比较数。我将他的代码格式化为:

status_t index_of_max_3(a,b,c)
{
    if (a > b) {
        if (a == c) return DONT_KNOW; // max a or c
        if (a >  c) return MOSTLY_A ;
        else        return MOSTLY_C ;
    } else {
        if (a == b) return DONT_KNOW; // max a or b
        if (b >  c) return MOSTLY_B ;
        else        return MOSTLY_C ;
    }
}

对于您的问题,我们不在乎是否进行相等性测试,因此我们可以省略2个测试。

如果得到错误的答案,代码有多干净/不好都没关系,因此这是您正确处理所有情况的好兆头!

接下来,为简单起见,人们一直试图“改善”答案,他们认为改善意味着“优化”比较次数,但严格来说,这并不是您要问的。您在询问“我觉得可能会有更好”的每个人面前感到困惑,但没有定义“更好”的含义。比较少?更少的代码?最佳比较?

现在,由于您询问的是代码的可读性(给定的正确性),因此我仅对代码进行一次更改以提高可读性:将第一个测试与其他测试对齐。

        if      (a > b && a > c)
            status = MOSTLY_A;
        else if (b > a && b > c)
            status = MOSTLY_B;
        else if (c > a && c > b)
            status = MOSTLY_C;
        else
            status = DONT_KNOW; // a=b or b=c, we don't care

我个人将以以下方式编写它,但这对于您的编码标准而言可能太过传统了:

        if      (a > b && a > c) status = MOSTLY_A ;
        else if (b > a && b > c) status = MOSTLY_B ;
        else if (c > a && c > b) status = MOSTLY_C ;
        else /*  a==b  || b ==c*/status = DONT_KNOW; // a=b or b=c, we don't care

脚注:这是生成排列的C ++代码:

#include <stdio.h>

char txt[]  = "< == > ";
enum cmp      { LESS, EQUAL, GREATER };
int  val[3] = { 1, 2, 3 };

enum state    { DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C };
char descr[]= "??A B C ";

cmp Compare( int x, int y ) {
    if( x < y ) return LESS;
    if( x > y ) return GREATER;
    /*  x==y */ return EQUAL;
}

int main() {
    int i, j, k;
    int a, b, c;

    printf( "a ?? b | a ?? c | b ?? c |P#| State\n" );
    for( i = 0; i < 3; i++ ) {
        a = val[ i ];
        for( j = 0; j < 3; j++ ) {
            b = val[ j ];
            for( k = 0; k < 3; k++ ) {
                c = val[ k ];

                int cmpAB = Compare( a, b );
                int cmpAC = Compare( a, c );
                int cmpBC = Compare( b, c );
                int n     = (cmpBC * 9) + (cmpAC * 3) + cmpAB; // Reconstruct unique P#

                printf( "%d %c%c %d | %d %c%c %d | %d %c%c %d |%2d| "
                    , a, txt[cmpAB*2+0], txt[cmpAB*2+1], b
                    , a, txt[cmpAC*2+0], txt[cmpAC*2+1], c
                    , b, txt[cmpBC*2+0], txt[cmpBC*2+1], c
                    , n
                );

                int status;
                if      (a > b && a > c) status = MOSTLY_A;
                else if (b > a && b > c) status = MOSTLY_B;
                else if (c > a && c > b) status = MOSTLY_C;
                else /*  a ==b || b== c*/status = DONT_KNOW; // a=b, or b=c

                printf( "%c%c\n", descr[status*2+0], descr[status*2+1] );
            }
        }
    }
    return 0;
}

编辑:根据反馈,将TL:DR移到顶部,删除了未排序的表,明确了27,清理了代码,描述了不可能的状态。


-1:减少决策数量是否会导致更简单的代码路径和更易读的代码?您的论点不清楚:首先,您说每个人都是错误的;然后,您放置的不是一两张桌子,而是三张桌子;我希望它们会导致一种更简单的方法来计算结果,但您却确认了其他所有人都已经知道的内容(OP的代码做了正确的事情)。当然,问题是关于可读性的,但是可读性不是仅通过修改代码布局来实现的(您承认所做的更改几乎不适合现有的代码标准)。在优化可读性时简化逻辑是有意义的。
coredump

更具建设性:我建议通过省略一些细节并考虑答案的结构来简化您的答案。我感谢您花时间编写和发布生成排列的C ++代码,但是也许您可以给出主要结果并且只有一张表:就目前而言,看起来您像是按原样转储了所有工作。我几乎没发现TL; DR的事情(您可以从此开始)。希望能帮助到你。
coredump

2
感谢您的建设性反馈coredump。由于容易验证,因此我删除了中间的未排序表。
Michaelangel007

2
耶稣基督!谁会说比较三个数字几乎和火箭科学一样复杂?
Mandrill

@Mandrill作为计算机科学家,彻底了解问题是我们的工作。只有通过列举所有27种可能的排列以进行三向比较,我们才能验证我们的解决方案在所有情况下均有效。作为程序员,我们想要的最后一件事是隐藏的bug和无法解释的极端情况。诚实是人们为正确性付出的代价。
Michaelangel007

5

@msw告诉您使用数组而不是a,b,c,而@Basile告诉您将“ max”逻辑重构为函数。将这两个想法结合起来

val[0] = countAs();    // in the real code, one should probably define 
val[1] = countBs();    // some enum for the indexes 0,1,2 here
val[2] = countCs();

 int result[]={DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C};

然后提供一个计算任意数组的最大索引的函数:

// returns the index of the strict maximum, and -1 when the maximum is not strict
int FindStrictMaxIndex(int *values,int arraysize)
{
    int maxVal=INT_MIN;
    int maxIndex=-1;
    for(int i=0;i<arraysize;++i)
    {
       if(values[i]>maxVal)
       {
         maxVal=values[i];
         maxIndex=i;
       }
       else if (values[i]==maxVal)
       {
         maxIndex=-1;
       }
    }
    return maxIndex;
}

并称它为

 return result[FindStrictMaxIndex(val,3)+1];

LOC的总数似乎比原始LOC的总数有所增加,但是现在您已将核心逻辑包含在可重用的函数中,并且如果可以多次重用该函数,那么它就会开始得到回报。而且,该FindStrictMaxIndex功能不再与您的“业务需求”交织在一起(关注点分离),因此您以后不得不对其进行修改的风险比原始版本要低得多(开放-封闭原则)。例如,即使参数数量发生变化,该函数也不必更改,或者不需要使用MOSTLY_ABC以外的其他返回值,或者您正在处理a,b,c以外的其他变量。此外,使用数组代替3个不同的值a,b,c可能还会在其他地方简化代码。

当然,如果在您的整个程序中只有一个或两个地方可以调用此函数,并且您没有任何其他应用程序来将值保存在数组中,那么我可能会保留原始代码(或使用@coredump的改进)。


我喜欢-的胆量FindStrictMaxIndex()可能不太干净,但是从调用者的角度来看,正在设法实现的目标是相当明显的。
肯·YN 2015年

或者,不持有两个数组,而是持有一个键-值对数组:{MOSTLY_A,countAs()},采用按值排序的第一个元素并读取键。
朱莉娅·海沃德

@JuliaHayward:我不建议采用这种解决方案的主要原因是问题的“ C”标记-在C语言中,将需要更多样板代码来处理键值对,并创建一个以KVP形式键入的函数在不同的上下文中可能不会像简单的int类型化函数那样可重用。但是,如果有人使用另一种语言(例如Python或Perl),我同意您的评论。
布朗

1
@ gnasher729:这取决于原始代码中有多少个“重复项”,它们实际有多相似以及FindStrictMaxIndex函数可重复使用的频率。对于一两次重复使用,这当然不会奏效,但这就是我在回答中已经写过的内容。还请注意我上面提到的有关将来更改的其他优点。
布朗

1
...,请注意,return result[FindStrictMaxIndex(val,3)]; 在代码中放置原始8行的位置,可以用简单的单行替换原始8行。其他部分,特别是FindStrictMaxIndex本身,与“业务逻辑”完全分开,这使它们脱离了更改业务需求的重点。
布朗

-1

您可能应该使用宏或MAX 提供最多两个数字的函数。

然后,您只想要:

 status = MAX(a,MAX(b,c));

您可能已经定义

 #define MAX(X,Y) (((X)>(Y))?(X):(Y))

但在使用宏时要谨慎(尤其要注意副作用)(因为MAX(i++,j--) 行为会很奇怪)

所以最好定义一个函数

 static inline int max2ints(int x, int y) {
    return (x>y)?x:y;
 }

并使用它(或至少#define MAX(X,Y) max2ints((X),(Y))..)

如果您需要了解MAX的来源,则可以使用一个长宏,例如 #define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) long do{... }while(0) 宏,也许

#define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) do { \
  int x= (X), y= (Y), z=(Z); \
  if (x > y && y > z) \
    { Status = MOSTLY_FIRST; Result = x; } \
  else if (y > x && y > z) \
    { Status = MOSTLY_SECOND; Result = y; } \
  else if (z > x && z > y) \
    { Status = MOSTLY_THIRD; Result = z; } \
  /* etc */ \
  else { Status = UNKNOWN; Result = ... } \
} while(0)

然后,您可以COMPUTE_MAX_WITH_CAUSE(status,res,a,b,c) 在几个地方调用。这有点丑陋。我定义的局部变量xyz 降低不良副作用....


2
将通用逻辑重构为一个函数是正确的方法,但是在这里我实际上避免了两件事:1.我不会“发明”新的要求(OP并没有要求计算最大值)。第二点:即使最终的代码可能变得更干燥,如果这证明了一个复杂的宏的合理性,这也是非常有争议的。
布朗

1
宏应该是万不得已的工具。绝对超出此问题的范围。
凯文·克莱恩

-1

我对此进行了更多的思考,因此由于我的问题主要是视觉确认所有比较均使用相同的变量,因此我认为这可能是一种有用的方法:

a = countAs();
b = countBs();
c = countCs();

if (FIRST_IS_LARGEST(a, b, c))
    status = MOSTLY_A;
else if (SECOND_IS_LARGEST(a, b, c))
    status = MOSTLY_B;
else if (THIRD_IS_LARGEST(a, b, c))
    status = MOSTLY_C;
else
    status = DONT_KNOW; /* NO_SINGLE_LARGEST is a better name? */

每个宏都采用ab并且c以相同的顺序进行确认很容易,而且宏名称使我省去了所有比较和操作的工作。


1
(1)为什么用辅助宏代替函数?(2)为什么在这里需要视觉确认?这真的是您的核心问题吗?还是需要视觉确认是代码重复的结果?最好的选择是将代码分解成一个简单的函数,然后对它进行一次检查。
coredump
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.