Большое если еще заявление - PullRequest
6 голосов
/ 08 июня 2011

Если есть большое (около 100 плюс) выражение if, как показано ниже, и условие 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 ]

7 голосов
/ 08 июня 2011

Есть много способов сделать это проще, например:

  • Вы можете заполнить карту из структуры, содержащей значения a, b, c и d для проверки вызываемой функции (код для заполнения карты все еще может быть беспорядочным, но выполнение будет быстрее и чище, можно добавить два ключа для дел 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).
  • для простого простого случая тестирования a, b, c и d один раз, используйте макрос ala #define ELIF_ABCD(A, B, C, D, F) else if (a == (A) && b == (B) && c == (C) && d == (D)) F();. При необходимости добавьте макросы для других комбинаций тестов, например, ABD, ABC, AD.
  • (может сделать код более загадочным), но может исследовать сдвиг битов и объединение значений в достаточно большой тип (int64_t), например, бинарный поиск по массиву указателей на функции

Однако следует обратить внимание на то, что цепочка if / else может содержать такие вещи, как:

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

Здесь порядок значим, поскольку входные данные могут совпадать с обоими. Этот аспект может легко потеряться или измениться, если поиск выполняется по-разному или используется какой-либо способ индексации указателя на функцию, что является одним из аргументов в пользу вышеописанного макроподхода.

3 голосов
/ 08 июня 2011

Следуя предложению Тони использовать карту, вы, вероятно, можете немного ее оптимизировать.

Вы можете закодировать все 4 числа как один uint64_t (или меньше, в зависимости от диапазона их значений).

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

Затем вы можете создать std::map<uint_64_t, void (*)()>, который отображает хэш на указатель на функцию.Хотя для создания карты может потребоваться некоторое усилие.Я думаю, что было бы лучше, если бы вы выслушали все другие предложения и реорганизовали ваш код.

1 голос
/ 09 июня 2011

Мечтал об этом вопросе за одну ночь .. и придумал изящное решение (вдохновленное соответствующими системами, используемыми в тесте Google 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 )
    ;

}

1 голос
/ 08 июня 2011

Чтобы сделать это правильно и эффективно, сначала необходимо стандартизировать представление каждой строки и преобразовать его в компактные данные, которые можно проиндексировать или отсортировать. Вы можете попытаться сделать это, просто сериализовав значения столбцов в строку фиксированной длины, а затем вставив эту строку и указатель для соответствующей функции в карту со строкой в ​​качестве ключа и указателем функции в качестве значения.

Однако проблема немного сложнее, потому что в некоторых строках некоторые столбцы не учитываются, они "не заботятся". Предполагая, что в каждом столбце нет значения, которое может выступать в качестве значения "не заботиться", в дополнение к значениям для каждого столбца ключ также должен содержать данные, указывающие, какие столбцы являются значимыми. Мы можем сделать это, добавив дополнительный байт к строке, которая содержит битовую маску, указывающую, какие столбцы являются значимыми. Чтобы в этом случае поиск по карте работал правильно, незначительные столбцы должны всегда содержать одно и то же значение в ключе (ноль - хороший выбор).

Теперь у нас есть только написать функцию для создания шестибайтового ключа из столбцов каждой строки нашей таблицы. Используйте эту функцию, чтобы выполнить начальную вставку карты и поиск после построения карты.

Этот метод довольно быстр для поиска, O (log n), где n - количество строк.

1 голос
/ 08 июня 2011

Чтобы развернуть первую точку Тони:

, вы можете заполнить карту из структуры, содержащей значения 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())();
1 голос
/ 08 июня 2011

Я бы подумал, что-то вроде этого - что поддерживает условия с функциями и значительно упрощает тестирование и расширение всего (на мой взгляд).

Возможно, вы можете создать подклассы, которые принимают параметры конструктора, чтобы уменьшить общее количество требуемых классов.

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; }
}
1 голос
/ 08 июня 2011

Разделите это на основе ваших 4 переменных

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

    }
    else if(b==3)
    {

    }
}
else if(a==3)
{

}

это немного упростило бы чтение и следование

0 голосов
/ 08 июня 2011

Это кажется очень грязным: /

Когда вам нужно описать бизнес-правила, вы должны использовать описательный подход, а не императивный подход.Это гораздо более читабельно, и, как правило, гораздо проще адаптировать правила.

Моя первая мысль - использовать таблицу, индексированную с помощью 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)
  • , включите b, c, d ....

Пример:

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, если это облегчит задачу.

Это по крайней мере так же гибко, как ваше текущее решение, но значительноболее читаемый / поддерживаемый.

0 голосов
/ 08 июня 2011

Без дополнительных подробностей можно только догадываться, как это упростить.

Одной из возможностей является использование логических переменных.Если вы постоянно оцениваете определенные комбинации, вы можете сохранить эту переоценку с помощью логических значений.

Если есть фиксированный набор условий, вы также можете использовать битовую маску против int, а затем выполнить case.

Но опять же, это всего лишь догадки, не зная больше подробностей о том, что вы на самом деле делаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...