如何高效识别字符串命令?

How to efficiently identify string commands?

给定一系列命令和非常独特的代码,每个代码必须 运行:

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。字符串命令列表在编译时是已知的,我可能会对哈希值进行硬编码……但这似乎不是一个好主意。

一种可能的方法是标记化:创建一个 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); };

如果您的 std::string 使用 SBO(几乎所有人都这样做),您还可以利用您的 std::string 具有最小保留大小的优势,并放弃上面的大小检查以获得更高的性能。

将结果保持在明显的严格范围内,以使编译器支持跳转-table。

接下来,使用宏来避免重复:

#define CASE(s) \
        break; \
    case hasher(s): \
        if (std::string_view(s) != cmd) \
            break;

是的,您不能使用(错误?)功能失败。如果您真的想要其中之一,请使用goto

现在您可以编写 switch 语句了:

switch (hasher(cmd)) {
CASE("cmd.setBoosterRocket")
    ...
CASE("cmd.windSales")
    ...
CASE("cmd.selfDustruct")
    ...
CASE("cmd.unleashHounds")
    ...
...
}

大多数编译器会就重复的 case 语句向您发出警告,因此 fiddle 使用散列,直到您获得便宜、独特且紧密绑定的散列。

不要忘记#undef 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,constexpr 可用于定义 index-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;
}

对于 quick_index,不必是 constexpr,您可以例如使用 unordered_map 在 O(1).

中查找字符串的索引

这是一个使用二进制搜索的完整示例(constexpr,因此它可以同时用于 indexquick_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;
    }
}