Как эффективно идентифицировать строковые команды?
Учитывая серию команд и очень уникальный код, который необходимо запускать для каждой:
if(cmd == "cmd.setBoosterRocket")
...
else if(cmd == "cmd.windSales")
...
else if(cmd == "cmd.selfDustruct")
...
else if(cmd == "cmd.unleashHounds")
...
Как это можно оптимизировать? Быть помещенным в оператор switch, то есть?
Я подумал о создании вектора хешей:
std::hash<std::string> hasher;
for(std::string command : m_commandList)
m_mashes.push_back(hasher(command)
Но к вектору нельзя получить доступ как часть оператора switch case, поскольку он не является constexpr. Список строковых команд известен во время компиляции, и я потенциально мог бы жестко закодировать хеш-значения ... но это не кажется хорошей идеей.
4 ответа
Один из возможных подходов - токенизация: сделать
enum
типа и словаря. Таким образом, вы воспользуетесь преимуществом переключателя (более удобным для программиста и компилятора способом, чем жестко запрограммированные хэши) и получите только логарифмическую сложность.
enum Command {SET_BOOSTER_ROCKET, WINDSALES, ETC};
const std::map<std::string, Command> commands = {
{"cmd.setBoosterRocket", SET_BOOSTER_ROCKET},
{"cmd.windsales", WINDSALES},
{"othercommands", ETC},
};
А потом
auto cmd_it = commands.find(cmd);
if(cmd_it == commands.end()) { // ERROR }
switch(cmd_it->second){
case SET_BOOSTER_ROCKET:
// your code
break;
case WINDSALES:
// your code
break;
// etc
}
Подобная токенизация ваших команд может быть немного утомительной, если вам нужно много чего начать, но тогда у нее есть хороший баланс между масштабируемостью и удобочитаемостью.
Что ж, напишите простую хеш-функцию для вашего варианта использования:
static constexpr hasher = [](auto&& s){ return char(s.size() < xyz ? 0 : expr); };
Вы также можете воспользоваться своим
Сохраняйте результаты в явно жестких рамках, чтобы компилятор предпочел таблицу переходов.
Затем используйте макрос, чтобы избежать повторения:
#define CASE(s) \
break; \
case hasher(s): \
if (std::string_view(s) != cmd) \
break;
А теперь вы можете написать свой switch-оператор:
switch (cmd) {
CASE("cmd.setBoosterRocket")
...
CASE("cmd.windSales")
...
CASE("cmd.selfDustruct")
...
CASE("cmd.unleashHounds")
...
...
}
Большинство компиляторов могут предупредить вас о дублировании case-операторов, поэтому возитесь с хешем, пока не получите дешевый, уникальный и плотно связанный.
Не забывай
Другой способ - создать отдельные функции для обработки каждого случая, а затем создать карту в качестве «диспетчера»:
const static std::unordered_map<std::string, void(*)()> dispatcher{
{ "cmd.setBoosterRocket", &setBoosterRocketHandler },
{ "cmd.windSales", &windSalesHandler },
{ "cmd.selfDustruct", &selfDustructHandler },
{ "cmd.unleashHounds", &sunleashHoundsHandler },
};
auto const it = dispatcher.find(cmd);
if (it == dispatcher.end()) { /* handle default case */ }
else { (*it)(); }
В C ++ 20 можно использовать для определения -lambda, чтобы сделать это эффективным.
static constexpr auto commands = { "cmd.setBoosterRocket", "cmd.windSales", ... };
constexpr auto index = [&](std::string s) -> size_t { /*...*/ };
switch(quick_index(cmd))
{
case index("cmd.setBoosterRocket"):
// ...
break;
default:
// ...
break;
}
Для, чего не должно быть, вы можете, например, использовать
unordered_map
чтобы найти индекс строк в O(1).
Вот полный пример использования двоичного поиска (который
constexpr
, поэтому его можно использовать как для
index
а также
quick_index
).
#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>
using namespace std;
template<class ForwardIt, class T, class Compare = std::less<>>
constexpr ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp = {})
{
first = std::lower_bound(first, last, value, comp);
return first != last && !comp(value, *first) ? first : last;
}
int main()
{
constexpr auto sorted = [](array<string_view, 3> a) {sort(a.begin(), a.end()); return a;};
static constexpr auto commands = sorted({ "bar", "foo", "foobar" });
constexpr auto index = [&](string_view s)
{ return binary_find(commands.begin(), commands.end(), s)-commands.begin(); };
string_view cmd = "bar";
switch(index(cmd))
{
case index("bar"):
cout << "bartender" << endl;
break;
case index("foo"):
cout << "fighter" << endl;
break;
case index("foobar"):
cout << "fighter bartender" << endl;
break;
default:
cout << "moo" << endl;
break;
}
}