Рефакторинг структуры 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 ....
}
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)Я бы также кодировал условия аналогичным образом. Например: условие
Как вы видите, после этого кодирования вы можете удалить оставшиеся ветви таким же образом, как и первые 6 ветвей.(a > N1) && ( b > N2)становитсяarg >= 110000и т. д.Если эффективность является главной проблемой, вы можете сделать тот же трюк с степени двух (битовые сдвиги, а затем использовать битовые маски). Я не занимаюсь мелкой возней, поэтому не могу помочь вам в этом, но это работает так же, и это более эффективно. Вероятно, так же эффективно, как иметь такую длинную цепочку "если-то", что у вас есть сейчас, или, возможно, даже более эффективно, поскольку вы не повторяете одни и те же сравнения.
Надеюсь, это поможет.
Если существует много правил, объекты функций (например: лямбды) могут быть там для спасения:
Я немного упростил его для демонстрационных целей, всего 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