大if if else声明

时间:2011-06-08 02:42:40

标签: c++ algorithm

如果有一个大的(大约100加)if else语句如下,if else条件可能是不规则的(例如一些取决于3个变量,有些取决于4),有什么方法可以使它变得更简单?

基本上我有一个大约100多行的表,其中a,b,c和d为列。基于a,b,c和d,我需要执行3种不同类型的功能。

该表描述了一套业务规则。

 uint8 a;
 uint8 b;
 uint16 c;
 uint8  d;

 if      ( a == 1 &&   b == 1           && c == 0)           { functionA();}
 else if ( a == 5 &&   b == 5           && c == 2 && d == 2) { functionB();}
 else if ( a == 1 && (b ==36 || b == 7) && c == 0)           { functionC();}  
 else if ( a == 3 &&   b == 3                     && d == 50) {functionA();}
    :
    :

9 个答案:

答案 0 :(得分:7)

有很多方法可以简化它,例如:

  • 您可以从包含abcd值的结构中填充地图,以检查要调用的函数(要填充的代码)地图可能仍然是一团糟,但它会更快更清晰地执行;可以为案例ala添加两个键b == x || b == y
  • 您可以手动分析条件:给出您的示例if (a == 1) if (b == 1 && c == 0) functionA(); else if ((b == 36 || b == 7) && c == 0) functionC();switch陈述可能会使这更清洁。在这样的分解中,您还可以使用<<=>和/或>=来划分更大的搜索空间,从而将性能从O(N)提高到O(log2N)。
  • 对于测试abcd的常见简单案例,请使用宏ala #define ELIF_ABCD(A, B, C, D, F) else if (a == (A) && b == (B) && c == (C) && d == (D)) F();。根据需要为其他测试组合添加宏,例如ABDABCAD
  • (可能使代码更加神秘),但可以探索位移和将值组合成足够大的类型(int64_t),然后例如。二进制搜索函数指针数组

要注意的是,if / else链可能包含以下内容:

if (a == 1 && c == 3 && d == 2) functionY();
else if (a == 1 && b == 2 && c == 3) function X();

这里,顺序很重要,因为输入可以匹配两者。如果不同地考虑搜索或者使用某种方式对函数指针进行索引,这个方面很容易丢失或改变,这是支持上述宏方法的一个论据。

答案 1 :(得分:3)

根据Tony建议使用地图,你可能会对它进行一些优化。

您可以将所有4个数字编码为单个uint64_t(或更小,具体取决于其值的范围)。

uint64_t h = (a << 32) + (b << 24) + (c << 8) + d;

然后,您可以构建一个将哈希映射到函数指针的std::map<uint_64_t, void (*)()>。但是,构建地图可能需要一些努力。我认为最好听听每个人的其他建议并重构你的代码。

答案 2 :(得分:1)

根据您的4个变量将其拆分

if(a==1)
{
    if(b==1)
    {

    }
    else if(b==3)
    {

    }
}
else if(a==3)
{

}

这会使阅读和关注更简单

答案 3 :(得分:1)

我会考虑这样的事情 - 它保持了功能的条件,使得整个测试和扩展更容易(在我看来)。

您可以生成带有构造函数参数的子类,以减少所需的类总数。

class ICase
{
  virtual ~ICase() {}
  virtual bool matches_arguments( int a, int b, int c ) const =0;
  virtual void do_it( int a, int b, int c)=0;
};

class CaseA : public ICase
{
  bool matches_arguments( int a, int b, int c ) const { return ( a == 1 &&   b == 1           && c == 0); }
  bool do_it(int a, int b, int c) { functionA(); }
};

...
//Some setup - only need to do this once
std::vector< shared_ptr<ICase> > cases;
cases.push_back( new CaseA );
cases.push_back( new CaseB );

//The conditionals
for( int i=0; i<cases.size(); ++i)
{
  if( cases[i]->matches_arguments(a,b,c) ) { cases[i]->do_it(a,b,c); break; }
}

答案 4 :(得分:1)

扩展托尼的第一点:

  

您可以从包含a,b,c和d值的结构填充地图,以检查要调用的函数

将所有变量包装在状态对象或其他内容中:

struct state {
    uint8 a;
    uint8 b;
    uint16 c;
    uint8 d;
}

并将一堆可能的状态添加到列表中:

std::set<state> functionASet;
functionASet.insert(aState);
...

然后测试该集是否包含由a, b, c, d

的当前值构成的状态
// init a state struct with current values for a, b, c, d
if(functionASet.find(currentValues) != functionASet.end())
    functionA();
else if(functionBSet.find(currentValues) != functionASet.end())
    functionB();
else ...

或者,将状态添加到地图中:

typedef void (*func)();

std::map<state, func> functionMap;

只需调用与找到状态匹配的函数:

std::map<state, func>::iterator search = functionMap.find(currentValues);
if(search != functionMap.end())
    (search->second())();

答案 5 :(得分:1)

要正确有效地执行此操作,首先需要标准化每行的表示形式并将其转换为可以编制索引或排序的紧凑数据。您可以尝试通过简单地将列的值序列化为固定长度的字符串,然后将此字符串和适当函数的指针插入到以字符串作为键并将函数指针作为值的映射中来执行此操作。 / p>

然而,问题有点复杂,因为在某些行中,某些列不计算,它们“不关心”。假设每列中没有值可以作为“不关心”值,除了每列的值之外,该键还必须包含指示哪些列是重要的数据。我们可以通过向字符串附加一个额外的字节来实现这一点,该字符串包含指示哪些列有效的位掩码。为了使映射搜索在这种情况下正常工作,无关紧要的列必须始终在键中包含相同的值(零是一个不错的选择)。

现在我们只编写一个函数来从表的每一行的列构造一个六字节的键。使用此函数在构建地图后执行初始地图插入和查找。

此方法对于查找非常快,O(log n),其中n是行数。

答案 6 :(得分:1)

一夜之间梦见这个问题..并提出了一个简洁的解决方案(灵感来自谷歌测试ilbraries中使用的匹配系统)

核心如果变得像这样的东西 - 我认为这很漂亮。

  Params(1,2,3,4)
    .do_if( match(1,_,3,5), functionA )
    .do_if( match(1,_,3,4), functionB )
    .do_if( match( _, OR(2,3),3,5), functionC )
//    .do_if( match(1,_,4,6)|match(3,_,5,8) ), functionD )
    ;

最后一行我还没有实现。 _表示匹配任何数字,OR表示匹配(尽管您可以将其嵌套OR(1,OR(2,3))应该没问题。

其余的支持代码是一堆模板函数,以使其工作。如果有兴趣的话,我可以对发生的事情进行更全面的描述......但它并不过分复杂 - 只是很长。我希望它也可以清理一下......

它可能被拔出并推广到一个很好的库中 - 尽管我可能会考虑调整google测试代码而不是基于此代码;)

struct RawParams
{
  RawParams( int a, int b, int c, int d) : a_(a), b_(b), c_(c), d_(d) {}
  int a_,b_,c_,d_;
};

struct ParamsContinue
{
  RawParams * p_;

  ParamsContinue() : p_(0) {}
  ParamsContinue( RawParams * p ) : p_(p) {}

  template<typename CONDITION, typename FN>
  ParamsContinue do_if( CONDITION cond, FN fn )
  {
    if( !p_ ) { return ParamsContinue(); }
    if( cond(p_->a_,p_->b_,p_->c_,p_->d_) ) { fn(); return ParamsContinue(); }
    return *this;
  }
};

struct Params
{
  Params( int a, int b, int c, int d) : params_(a,b,c,d) {}
  RawParams params_;

  template<typename CONDITION, typename FN>
  ParamsContinue do_if( CONDITION cond, FN fn )
  {
    return ParamsContinue(&params_).do_if(cond,fn);
  }
};

struct AnySingleMatcher
{
  bool operator()(int i) const { return true; }
};

AnySingleMatcher _;

template<typename M1, typename M2, typename M3, typename M4>
struct Match
{
  Match( M1 in_m1, M2 in_m2, M3 in_m3, M4 in_m4 ) : 
    m1(in_m1),
    m2(in_m2),
    m3(in_m3),
    m4(in_m4)
  {}

  bool operator()( int a, int b, int c, int d) const { return m1(a)&&m2(b)&&m3(c)&&m4(d); }

  M1 m1;
  M2 m2;
  M3 m3;
  M4 m4;
};

struct AnyMatcher {};
struct IntMatcher
{
  IntMatcher(int i) : i_(i) {}
  bool operator()(int v) const { return v==i_; }
  int i_;
};

template<typename T>
struct as_matcher
{
  typedef T type;
  static T as( T t ) { return t; }
};

template<>
struct as_matcher<int>
{
  typedef IntMatcher type;
  static IntMatcher as( int i ) { return IntMatcher( i ); }
};

template<typename M1, typename M2, typename M3, typename M4 >
Match< typename as_matcher<M1>::type, typename as_matcher<M2>::type, typename as_matcher<M3>::type, typename as_matcher<M4>::type >
match( M1 m1, M2 m2, M3 m3, M4 m4 )
{
  return 
    Match< typename as_matcher<M1>::type, typename as_matcher<M2>::type, typename as_matcher<M3>::type, typename as_matcher<M4>::type >(
      as_matcher<M1>::as(m1), as_matcher<M2>::as(m2), as_matcher<M3>::as(m3), as_matcher<M4>::as(m4) );
};

template<typename T1, typename T2>
struct OrMatcher
{
  OrMatcher( T1 t1, T2 t2 ) : t1_(t1), t2_(t2) {}
  T1 t1_;
  T2 t2_;
  bool operator()(int i) const { return t1_(i) || t2_(i); }
};

template<typename T1, typename T2>
OrMatcher< typename as_matcher<T1>::type, typename as_matcher<T2>::type > OR( T1 t1, T2 t2 )
{
  return OrMatcher< typename as_matcher<T1>::type, typename as_matcher<T2>::type >( as_matcher<T1>::as(t1),as_matcher<T2>::as(t2) );
};

#include <iostream>
void functionA(){ std::cout<<"In A"<<std::endl;};
void functionB(){ std::cout<<"In B"<<std::endl;};
void functionC(){ std::cout<<"In C"<<std::endl;};
void functionD(){ std::cout<<"In D"<<std::endl;};

int main()
{
  Params(1,2,3,4)
    .do_if( match(1,_,3,5), functionA )
    .do_if( match(1,_,3,4), functionB )
    .do_if( match( _, OR(2,3),3,5), functionC )
//    .do_if( match(1,_,4,6)|match(3,_,5,8) ), functionD )
    ;

}

答案 7 :(得分:0)

如果没有进一步的细节,人们只能猜测如何简化它。

一种可能性是使用布尔变量。如果您经常评估某些组合,可以使用布尔值来保存重新评估。

如果有一组固定的条件,您还可以对int使用位掩码,然后执行case

但同样,这些只是猜测而不了解你正在做什么的更多细节。

答案 8 :(得分:0)

看起来很乱:/

当您需要描述业务规则时,您应该使用描述性方法而不是命令式方法。它更具可读性,通常更容易适应规则。

我的第一个想法是使用一个表,由a,b,c和d索引,并在其中包含函数指针(或函子)。

初始化代码会有点可怕,我的建议是按字典顺序排序:

// Note: you don't have to initialize the null values if the variable is static
Table[0][0][0][1] = functionA;
Table[0][3][0][1] = functionB;
...

检索该函数是一块蛋糕,只要记住如果没有函数可以测试无效(否则会assert)。

另一种解决方案是使用函数将选择分解为步骤:

  • 启用a,选择要调用的函数(使用default处理不关心a的情况)
  • 启用bcd ....

示例:

void update(int a, int b, int c, int d) {
  switch(a) {
  case 0: updateA0(b, c, d); break;
  case 1: updateA1(b, c, d); break;
  default: updateAU(b, c, d); break;
  }
}

void updateA0(int b, int c, int d) {
  switch(b) {
  case 0: updateA0B0(c, d); break;
  case 1: updateA0B1(c, d); break;
  default: updateA0BU(c, d); break;
  }
}

// etc...

它可以更轻松地“关注”更新链,并应用本地更新。此外,逻辑适用于每个子功能,很容易应用范围选择(if (b >= 5 && b < 48))而不“破坏”模式或复制初始化。最后,根据某些路径上的可能性,您可以轻松选择d中的updateA1,如果它更容易。

它至少与您当前的解决方案一样灵活,但更具可读性/可维护性。