Рефакторинг структуры if-else-if



Как я могу изменить следующий код C++? Я использую C++11 в своей программе



void f(int a, int b, int c, int d, int e, int f) {

// MAX1..MAX6 are constants, N1..N6 are constants
if( a > MAX1) {
.. code block 1..
}
else if(b > MAX2) {
.. code block 2..
}
else if(c > MAX3) {
.. code block ..
}
else if(d > MAX4) {
.. code block 3 ..
}
else if(e > MAX5) {
.. code block 4 ..
}
else if(f > MAX6) {
.. code block5 ..
}
else if(( a > N1) && ( b > N2)) {
.. code block 6 ..
}
else if(( c > N3) && ( d > N4)) {
.. code block 7 ..
}
else if (( e > N5) && ( f > N6)) {
.. code block 8 ..
}
else if (( a > N1) && ( b > N2) && (d > N4)) {
.. code block 9 ..
}
else if (..some combination of (a > N1) to (f > N6)...) {
// there are more than 30 such combinations in my code
.. code block XX ..
}
else {
.... code block ....
}
598   6  

6 ответов:

В функции будет не более 64 блоков кода, каждый из которых может использовать анонимную функцию или именованную функцию:

my_func_array[64] = {
    [CONDITION(1,0,0,0,0,0)] = { codeblock1 },
    [CONDITION(0,1,0,0,0,0)] = { codeblock2 },
     ...
};

Макрос будет в основном объединять 6 первых входных данных в индекс, по существу переводя в:

 my_func_array[64] = {
    [32] = { ... },
    [16] = { ... },
 };
Это означает, что вам не нужно вводить условия в каком-либо определенном порядке...

При выполнении вы должны также оценить все условия:

 int condition = CONDITION(a < MAX1, b < MAX2, c < MAX2, ...);
 if (my_func_array[condition])
      my_func_array[condition]();
 else
 {
      // this second block should cover all the other cases
      int condition2 = CONDITION(a < N1, b < N2, c < N3, ... );
      if (my_func_array2[condition2])
          my_func_array2[condition2]();
 }

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

Перечисление классов CaseSelect {CASE0, CASE1, CASE2};

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

Затем создайте оператор case, используя перечисления, с соответствующей логикой в каждом

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

Вы, конечно, можете начать, сделав a через f, MAX, и N в массивы (только начальный эскиз, который я могу расширить с дополнительной информацией о ваших условиях, блоках кода и использовании):

const int MAX[6] = { ... };
const int N[6] = { ... };
const std::function funcs[6];

void f(int in[6])
{
    for(int i = 0; i < 6; ++i)
    {
        if(in[i] > MAX[i])
        {
            funcs[i]();
            break;
        }
    }
}

Итак, предположим, что у вас есть дискретное число N взаимоисключающих целочисленных диапазонов (xi-yi), и вы хотите вызвать некоторый блок кода в зависимости от того, в каком диапазоне находится данный входной сигнал z, тогда:

  • вы хотите использовать двоичное дерево std::map для хранения начала каждого диапазона и соответствующей лямбды.
  • вызовите std::lower_bound на z, чтобы найти диапазон кандидатов
  • и затем сверьте верхнюю границу этого диапазона с z.
  • если внутри, вызовите лямбду

Это даст O (log (N)) время по сравнению с O(n) для большой цепи if-else.

Во-первых, я бы избавился от первых 6 ветвей, как предлагает Марк B , но

  • Вместо break я бы return,

  • Я бы просто использовал указатели функций, std::function - это перебор здесь

  • Я бы использовал std::array и проверку привязки вместо необработанных массивов.

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

Я предполагаю, что MAX1 > N1. Я надеюсь, что это правда.

Сначала я построю десятичное число, кодируя все аргументы:

int arg=10^5*(a>N1?1:0)+10^4*(b>N2?1:0)+10^3*(c>N3?1:0)+10^2*(d>N4?1:0)+10*(e>N5?1:0)+(f>N1?1:0)

Я бы также кодировал условия аналогичным образом. Например: условие (a > N1) && ( b > N2) становится arg >= 110000 и т. д.

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

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

Надеюсь, это поможет.

Если существует много правил, объекты функций (например: лямбды) могут быть там для спасения:

Я немного упростил его для демонстрационных целей, всего 3 аргумента...

И я предполагаю, что есть безопасное значение для непроверки, в данном случае, 0 (мог бы жить без него, но тогда он выглядел бы уродливее).

#include <iostream>
#include <functional>

struct Rule {
    int a;
    int b;
    int c;
    std::function<int()> fun;
};

Rule rules[]{
    { 10, 0, 0, []() { std::cout << "First!"; return 0;} },
    { 0, 20, 0, []() { std::cout << "Second!"; return 1;} }
};

int f(int a, int b, int c) {
    for (Rule rule : rules) {
        if ((rule.a == 0 || a > rule.a)
         && (rule.b == 0 || b > rule.b)
         && (rule.c == 0 || c > rule.c))
            return rule.fun();
    }
    std::cout << "Not match!";
    return 2;
}

int main() {
    f(5, 23, 3);
};

Comments

    Ничего не найдено.