Как оптимизировать оператор вида "if (A == B) { ...} else if (A <B) {...} else {....}"
У меня есть кусок кода, который похож
if (A == B)
{
...
}
else if (A < B)
{
...
}
else // (A > B)
{
...
}
Я понимаю, что есть проблема избыточности, потому что будут некоторые из тех же самых битовых сравнений, идущих в вычислении ==
а также <
, Как я могу оптимизировать свой код, чтобы сделать его более интересным и быстрым?
2 ответа
Для C# вы можете использовать универсальную функцию, которая принимает 2 значения, а затем лямбда-действие для каждого случая.
void CompareAndAct<T>(T a, T b, Action fnEqualTo, Action fnLessThan, Action fnGreaterThan) {
var comparison = System.Collections.Generic.Comparer<T>.Default.Compare(a, b);
if (comparison == 0) {
fnEqualTo();
}
else if (comparison < 0) {
fnLessThan();
}
else { //A > B
fnGreaterThan();
}
}
Затем вы можете использовать его столько раз, сколько захотите:
CompareAndAct(a,b, () => Console.Writeline("Equal"),() => Console.WriteLine("Less Than", () => Console.WriteLine("Greater Than"));
Не могу сказать, что рекомендую это делать, но это сработает. Это не быстрее (возможно, медленнее), но я полагаю, можно сказать, что это "более модно".
Вы не указываете язык, но в зависимости от языка это может быть переписано разными способами
Рубиновый способ (с использованием оператора космического корабля):
case A <=> B
when -1 then... # A < B
when 0 then... # A = B
when 1 then... # A > B
end
Perl, PHP7 и Groovy также имеют один и тот же оператор. Многие другие языки имеют аналогичные операторы или функции для той же цели комбинированного сравнения, как cmp
в Python 2, compare
в OCaml и compareTo
в Котлине. C# не имеет этого оператора, но имеет IComparable
интерфейс с CompareTo
метод.
VB путь:
Select Case A
Case Is < B
...
Case Is = B
...
Case Is > B
...
End Select
В C, C++ и многих C-подобных языках без CompareTo
метод, которым вы можете использовать этот способ
int cmp = (A > B) - (A < B);
switch (cmp)
{
case -1: ...
case 0: ...
case 1; ...
}
Многие языки, такие как Java, не позволяют напрямую использовать результаты сравнения в виде числового значения. В этом случае вы можете использовать signum
функция
switch(Integer.signum(A - B))
Вы можете реализовать signum
легко функционировать в C и C++, как это
Это для языков высокого уровня. На уровне сборки все проще. В сборке x86 требуется только одно сравнение, тогда в зависимости от результата мы перейдем к соответствующему блоку, так что это не 3 сравнения, а компилятор достаточно умен, чтобы оптимизировать этот простой случай. Например:
cmp eax, ebx
je EQUAL_TO ; jump if =
ja GREATER_THAN ; jump if >
; less than case's code
jmp END_CMP
EQUAL_TO:
; equal case's code
jmp END_CMP
GREATER_THAN:
; larger than case's code
END_CMP:
То же самое с другими архитектурами с флагами сравнения, такими как ARM или 68k... Для архитектур без флага, такого как MIPS, вам может потребоваться еще одно сравнение, но не 3 сравнения
Пример MIPS:
beq $t0, $t1, EQUAL_TO # $t0 = A, $t1 = B; if ($t0 == $t1) equal();
slt $t0, $t1, $t2 # $t2 = is_less_than = ($t0 < $t1);
beq $t2, $zero, GREATER_THAN # if (!is_less_than) larger();
# "less than" code here
# ...
j END_CMP
EQUAL_TO:
# "equal" code
# ...
j END_CMP
GREATER_THAN:
# "larger" code
# ...
END_CMP:
Для архитектур с условными инструкциями, такими как ARM или Itanium и с достаточно простым телом в блоках if-else, вам может даже не потребоваться переход