带有字符串的长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

点赞
stackoverflow用户122757
stackoverflow用户122757

你始终可以尝试使用三元运算符进行操作。例如,不需要这样做:

   if(condition){
        a=b+c;
   }else{
        a=b+d;
   }

你可以替换成

   a=b+(condition?c:d);

在许多小的情况中,代码可以被缩减(以少量的可读性为代价,而没有实质性的速度提升)。但如果不行,则没有更好的方法可以做到这一点。唯一的例外是如果您可以将其描述为状态机,然后您可以使用一个switch-case系统。

2010-03-26 21:18:28
stackoverflow用户121747
stackoverflow用户121747

你可以建立一个自动机(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)。

2010-03-26 21:24:03
stackoverflow用户144846
stackoverflow用户144846

你可以尝试使用枚举和 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;
  ...
}
2010-03-26 21:26:59
stackoverflow用户257465
stackoverflow用户257465

简易方法:

一个简单的替代方法是使用你定义的 enum 来替换你的“行动字符串”;它将完全按照相同的方式工作,只是在 enum 上使用 switch 语句(本质上是一个整数)要比进行字符串比较快得多。相比于你所描述的 if-tree 结构,使用 switch 语句看起来也更美观( 个人意见 :))。

强大方法:

更简洁的解决方案是使用函数指针-只要你需要执行的所有不同操作都可以包含在具有相同签名的不同函数中,那么你只需绑定适当的函数,它会自动被调用。

2010-03-26 21:27:58
stackoverflow用户11758
stackoverflow用户11758

有一种名为trie的数据结构可以在内存高效地快速选择字符串。如果这是一个重要的性能瓶颈,并且传递字符串不可能更改,则可能会是正确的选择。

然而,我认为这太复杂了。如果保持Lua和C中的某种枚举同步,并对其进行switch case或创建跳转表,这将提高性能,并且更易于开发。

2010-03-26 21:38:06
stackoverflow用户44065
stackoverflow用户44065

由于您指定的是 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;
2010-03-26 21:51:50
stackoverflow用户68204
stackoverflow用户68204

由于你已经嵌入了 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 表中的所有函数,并在很大程度上规避这个问题。

2010-03-26 22:36:03
stackoverflow用户41661
stackoverflow用户41661

很难确定什么更好,因为您并没有很清楚地说明您的约束条件。其他答案展示了如果您愿意将更多的符号导出到 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

2010-03-26 23:26:59