带有字符串的长IF树
我有一个使用 Lua 进行脚本编写的 C 程序。为了保持可读性以及避免在各个 Lua 状态中导入多个常量,我使用一个“操作”字符串将大量的函数压缩成一个简单的调用(例如“ObjectSet(id,“ANGLE”,45))。
为了实现这一点,我使用一个大型 if 树将操作字符串与一个列表进行比较(例如“if(stringcompare(action,“ANGLE”)...else if (stringcompare(action,“X”)...等等)。
这种方法效果很好,在程序中它并不慢,并且很快添加一个新的动作。但我有一点完美主义。有没有更好的方法在 C 中实现这一点?
并且由于 Lua 在大量使用,也许有一种使用它来实现此目的的方法?(嵌入“块”创建一个字典?)虽然这一部分主要是出于好奇。
编辑:我应该指出我没有使用 C++。
原文链接 https://stackoverflow.com/questions/2526769
你可以建立一个自动机(automata),逐个字符检查,例如:
if (curChar == 'a')
{
curChar = next;
if (curChar == 'b')
{
// all strings that begin with "ab"
}
}
这将对每个字符的比较进行优化,复杂度为O(n)而非整个if链的O(n*m)。当然,你不必自己手动完成:你可以寻找一个自动化工具,从你需要的字符串构建匹配的自动机(Automaton)… 实际上,这与正则表达式匹配器所做的事情相似。
否则,如果你想要复杂度为O(1) O(nlogn)(抱歉,我以为它使用哈希表而不是二叉树),你可以使用std::map
,将字符串作为键,将函数指针作为值,它们表示每个不同的字符串执行的行为。以这种方式,使用O(1)获得正确的指针,然后调用该函数。
好吧,所以你只想要C。你将不得不实现自己的哈希映射(或使用预先存在的哈希映射之一,你可以找到许多)或二叉树结构。
使用前一种方法,你将实际上拥有O(1)复杂度(除非哈希映射与字符串数量相比太小),使用后一种方法将得到类似于自动机的方法。
对于哈希映射,你只需要:
- 计算字符串的哈希代码的方法:你可以使用
int hash = string[0]^31 + string[1]^30 + ...
来获得一个好的唯一数字,代表你的字符串。 - 然后使用一个好的哈希函数在此处检查,将哈希代码转换为指向函数指针的数组的索引,从而获得所需的元素。
- 你需要处理冲突的方法(两个字符串在哈希表相同的索引结束)
当然,这种解决方案对于像你这样的简单任务而言有点过剩,另一方面,它将很有趣,并且可以教你很多东西…但三思而后行,看看你真正是否需要它!
如果我建议使用C++但没有考虑你是在要求C,我很抱歉,std::map
是标准模板库的一个组成部分,用于存储值对 <key,value>
。在你的情况下,你将有<string,funcPointer>
,但如评论中所建议的,复杂度将为O(nlogn)。
你可以尝试使用枚举和 case 语句。虽然该函数将只是一个大型 case 语句替换了一个 if 树,但比较操作不需要每次都进行字符串比较,而只需要进行数值比较。
typedef enum objectsetAction {
objectset_angle = 0,
objectset_other,
...
} objectsetAction;
然后定义 ObjectSet 函数以获取一个 objectsetAction 参数而不是字符串。
这样,您只需要编写
switch(action) {
case objectset_angle:
//dostuff
break;
case objectset_other:
//dostuff
break;
...
}
简易方法:
一个简单的替代方法是使用你定义的 enum
来替换你的“行动字符串”;它将完全按照相同的方式工作,只是在 enum
上使用 switch
语句(本质上是一个整数)要比进行字符串比较快得多。相比于你所描述的 if
-tree 结构,使用 switch
语句看起来也更美观( 个人意见 :))。
强大方法:
更简洁的解决方案是使用函数指针-只要你需要执行的所有不同操作都可以包含在具有相同签名的不同函数中,那么你只需绑定适当的函数,它会自动被调用。
有一种名为trie的数据结构可以在内存高效地快速选择字符串。如果这是一个重要的性能瓶颈,并且传递字符串不可能更改,则可能会是正确的选择。
然而,我认为这太复杂了。如果保持Lua和C中的某种枚举同步,并对其进行switch case或创建跳转表,这将提高性能,并且更易于开发。
由于您指定的是 C 而不是 C++,因此对于已排序的并行数组:
#define VERB_COUNT 10
void *context_data;
char *verbs[VERB_COUNT] = { "ABC", "DEF", "GHI", ... }; // 排序列表
int (*actions[VERB_COUNT])(void *) = { abc_func, def_func, ghi_func, ... };
int idx, ret = -1;
int idx = bsearch(action, verbs, VERB_COUNT, sizeof char*, strcmp); // 我认为这些顺序是正确的
if (idx >= 0)
ret = (*actions[idx])(context_data);
return ret;
由于你已经嵌入了 Lua 并且可以使用它,所以你可以使用它。Lua 表是一种关联数组,可以被任何 Lua 值(除了 nil
)索引和存储任何值。字符串可以作为键,函数可以作为值。
你可以轻松地将像 ObjectSet(id, "ANGLE", 45)
这样的调用转换为 actions.ANGLE(id,45)
这样的调用。
为此,安排创建包含实现每个操作的函数的 actions
表。最简单的方法可能涉及一块 Lua 代码,用于初始化表,但也可以从 C 端完成。
actions = {
ANGLE = function(id,theta)
-- do something with id and theta
end,
X = function (id, x)
-- do something with id and x
end,
}
或者更清晰地表示为
module("actions")
function ANGLE(id,theta)
-- ...
end
function X(id,theta)
-- ...
end
从 C 中,你可以这样实现 ObjectSet()
(未经测试):
void ObjectSet(int id, const char *action, int arg) {
lua_getglobal(L,"actions");
lua_getfield(L,-1,action);
lua_remove(L,-2);
lua_pushinteger(L,arg);
if (lua_pcall(L,1,0,0)) {
fprintf(stderr, "Lua error: %s\n", lua_tostring(L,-1));
}
return;
}
实际的错误处理留作练习。请注意,此处使用了 lua_pcall()
,以便 Lua 错误不会传播出 ObjectSet()
。如果你使用的是 C++,你需要小心,因为 Lua 使用 setjmp()
和 longjmp()
处理错误,这些错误通常必须通过捕获 Lua 错误并抛出合适的异常来转换为 C++ 异常。
我也自然地将在 Lua 端将对象 id 与实际对象关联作为一个练习留给你。但是,你可以在 C 中实现 actions
表中的所有函数,并在很大程度上规避这个问题。
很难确定什么更好,因为您并没有很清楚地说明您的约束条件。其他答案展示了如果您愿意将更多的符号导出到 Lua 或将更多的工作委派给 Lua,您可以做些什么。我的答案回答了一个狭窄的问题:如果您不改变与 Lua 交互的方式,如何重构您的 C 代码?我建议您将代码制作成表驱动的。
这是一个设计草图:
typedef struct action {
const char *name;
int (*doit)(lua_State *L, int idx);
} *Action;
static Action my_actions[] = {
{ "angle", set_angle },
{ "color", set_color },
...
{ NULL, NULL }
};
int i_replace_nest_of_ifs(lua_State *L) {
const char *action = luaL_checkstring(L, 1);
for (int i = 0; my_actions[i].name && strcmp(my_actions[i].name, action); i++)
;
if (my_actions[i].name)
return my_actions[i].doit(L, 2);
else
return luaL_error("Asked for unknown action '%s'", action);
}
如果对动作进行线性搜索变得太昂贵,可以在打开库时按名称排序,然后调用 bsearch
。
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
你始终可以尝试使用三元运算符进行操作。例如,不需要这样做:
if(condition){ a=b+c; }else{ a=b+d; }
你可以替换成
在许多小的情况中,代码可以被缩减(以少量的可读性为代价,而没有实质性的速度提升)。但如果不行,则没有更好的方法可以做到这一点。唯一的例外是如果您可以将其描述为状态机,然后您可以使用一个switch-case系统。