【问题标题】:branching depending on which of 3 numbers is smallest分支取决于 3 个数字中的哪一个最小
【发布时间】:2016-09-20 19:22:39
【问题描述】:

解决方案很明显,但这是关于不错的解决方案的问题。

(编辑:nice 我的意思是例如 1)没有代码冗余 2)不影响性能 3)不强迫程序员制作一些不必要的函数或临时变量)

考虑一下我想根据abc 中哪个数字最小来执行 3 个不同代码块的情况。

代码如下所示:

    if( a < b ){
       if( a < c ){  
           // code block for "a is minimum" case
       }else{        
           // code block for "c is minimum" case
       }
    }else{
       if( b < c ){
           // code block for "b is minimum" case
       }else{    
           // code block for "c is minimum" case
       }
    }

我不喜欢的是我必须复制// code block for "c is minimum" case 两次。

有几种解决方案。例如。我可以将"c is minimum" 的代码块放入内联函数。但我不喜欢它(似乎不太清楚阅读)。

老派的解决方案是使用goto,例如:

    if( a < b ){
       if( a < c ){  
           // code for "a is minimum" case
           goto BRANCHE_END;
       }
    }else{
       if( b < c ){ 
           // code for "b is minimum" case
           goto BRANCHE_END;
       }
    }
   // code for "c is minimum" case
   BRANCHE_END:

但人们不喜欢看到 goto(有充分的理由)。另一方面,在这种特殊情况下,它的可读性甚至非常好。

如果代码块是独立的函数,它可以写成这样

 void myBranching( double a, double b, double c ){
    if( a < b ){
       if( a < c ){  
           // code for "a is minimum" case
           return;
       }
    }else{
       if( b < c ){ 
           // code for "b is minimum" case
           return;
       }
    }
   // code for "c is minimum" case
    return;
 }

(实际上与goto 几乎相同)但是在许多情况下,类似的代码块必须是更复杂算法的一部分,并且将其放入函数中很不方便。函数中的封装需要传递许多在内部和外部都使用的变量(例如,参见下面的用例)。

C/C++ 中是否有任何控制结构可以优雅地解决这个问题。

注意:考虑性能关键代码。 (例如光线追踪器、GLSL 着色器、物理模拟)。任何会增加一些不必要的计算开销的东西都是没有问题的。


其他问题/cmets

  • 这是我觉得Structured programming 束缚我的双手的几个例子之一,这只是jump 指令可能做的事情的一部分。您是否知道使用goto 而不是标准控制结构的算法会更简单和清晰的其他示例?
  • 您能想象更复杂的分支,需要多次复制某些代码块吗?

编辑:用例

我认为一些混乱是由于我没有指定我想使用它的上下文。这是光线追踪常规 triclinic 3D 网格的算法的一部分(类似于 3D 中的 Bresenham's line algorithm,但起点是浮动的(不与任何框的中心对齐))

但是请不要把注意力集中在算法本身上,它也可能是错误的,我正在调试它。

double pa,pb,pc,invPa,invPb,invPc,mda,mdb,mdc,tmax,t;
int ia,ib,ic; 
// for shortness I don't show initialization of these variables
while( t<tmax ){
    double tma = mda * invPa;
    double tmb = mdb * invPb;
    double tmc = mdc * invPc;
    if( tma < tmb ){
       if( tma < tmc ){  // a min
            t   += tma;
            mda  = 1;
            mdb -= pb*tma;
            mdc -= pc*tma;
            ia++;
       }else{            // c min
            t   += tmc;
            mda -= pa*tmc;
            mdb -= pb*tmc;
            mdc  = 1;
            ic++;
       }
    }else{
       if( tmb < tmc ){  // b min
            t   += tmb;
            mda -= pa*tmb;
            mdb  = 1;
            mdc -= pc*tmb;
            ib++;
       }else{            // c min
            t   += tmc;
            mda -= pa*tmc;
            mdb -= pb*tmc;
            mdc  = 1;
            ic++;
       }
    }
    // do something with ia,ib,ic,mda,mdb,mdc
}

【问题讨论】:

  • std::min 你在找什么吗?
  • 不,不是。 (顺便说一句。我正在查看以前提出的问题......主题最重要的是stackoverflow.com/questions/11833327/…
  • 你的问题的问题是 nice 并没有真正为每个人定义相同的。
  • @Drop > 这是真的,但代码重复不喜欢任何人(我猜)
  • 你觉得这个“好”吗:return (a &lt; b) ? ( (a &lt; c) ? a : b ) : c;?

标签: c++ decision-tree goto


【解决方案1】:

您可以使用std::min 和使用std::initializer_list 的版本轻松解决这个问题。您在三个变量上调用 min 以获得最小值,然后您有三个 if 语句来检查每个变量的返回值。

void foo(int a, int b, int c)
{
    int min = std::min({a, b, c});
    if (min == a)
    {
        // a case
    }
    else if (min == b)
    {
        // b case
    }
    else
    {
        // c case
    }
}

【讨论】:

  • 除非在一个非常紧凑的循环中使用,否则这样的事情(简单的整数比较)不会对性能至关重要。即使在一个紧密的循环中,我也相信我的编译器会用它做一些理智的事情,而不会费心去尝试对其进行微优化……至少除非我有分析数据支持它作为性能热点,否则不会。
  • @ProkopHapala 您要求不错的解决方案,恕我直言,这个很好。 +1。
  • @ProkopHapala 我认为你不能比每个变量 1 个 if 语句做得更好。
  • @Drop 您将使用比较器来检查它们是否在容差范围内接近,而不是严格相等。 Here 就是一个例子。
  • @Drop 抱歉,我明白你在说什么。忽略我所说的比较器。无论如何,您可以与 if 块中的容差进行比较。
【解决方案2】:

顺便说一句,你不必嵌套条件:

if(a <= b && a <= c) { /* a is the lowest */ }
else if(b <= c) { /* b is the lowest */ }
else { /* c is the lowest */ }

请注意,C++ 运算符 &amp;&amp; 的语义是这样的

if(a <= b && a <= c)

大致类似

if(a <= b) if(a <= c)

(不同之处在于后续的else 子句(如果有)同时涵盖ifs。)在表达式cond1 &amp;&amp; cond2 中,如果cond1 被证明为假,则永远不会评估cond2,至少,它没有明显的副作用。

当然,我们可以做一些更可怕的事情,比如非本地出口:

do {
    if(a <= b) {
        if(a <= c) {
            // a is the lowest
            break;
        }
    }
    else if(b <= c) {
        // b is the lowest
        break;
    }
    // c is the lowest
}
while(0);

但事实上,这个结构尽管高得令人难以置信,但在逻辑上等同于上面那三行(Dieter 建议的编辑)。

【讨论】:

  • 是的,但这仍会增加一些开销(2x &lt;= 而不是一个和一个 &amp;&amp;
  • 因为你似乎对性能很着迷。您是否查看过生成的 asm 以获取任何建议的解决方案?当然启用编译器优化......我的猜测是,使用现代编译器(clang 3.8,gcc 6.1 VC++2015)你不会看到太多(相关)差异。但请告诉我们。
  • 或者使用分析器确定这实际上是一个瓶颈,它可能不会。
  • @dwcanillas 显然。这是毫无意义的微优化,可能是浪费时间。剖析器很可能会指向要炸的大得多的鱼。
  • @JesperJuhl 还有一些有问题的编译器,比如 CUDA 的 nvcc、OpenCL 的东西、着色器编译器(在问题中提到)。不确定它是否相关。在这个问题中,“Nicety”对我来说仍然是 __undefined_symbol。
猜你喜欢
  • 1970-01-01
  • 2015-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-30
  • 2018-04-28
  • 1970-01-01
相关资源
最近更新 更多