【问题标题】:Call Python or Lua from C++ to evaluate an expression, calculating unknown variables only if needed从 C++ 调用 Python 或 Lua 来计算表达式,仅在需要时计算未知变量
【发布时间】:2017-09-16 22:14:07
【问题描述】:

我有一个这样的表达式/公式

 std::string expr="((A>0) && (B>5 || C > 10))";

我做了一些研究,似乎如果 A、B、C 值已知,通过在 C++ 程序中嵌入 Lua 或 Python,有 eval 函数可以替换 A、B 和 C 并返回 @987654324 @ 或false

但是当我不知道所有值时会发生什么?假设 A 是已知的并且它是-1。如果 A 为 -1,则无论 B 或 C 的值如何,公式都将评估为“假”。

我可以在事先不知道所有变量的情况下评估公式吗?例如,如果 A 为 10,则查找 B 的值并再次重新评估是有意义的。我们如何解决这些问题?想法?

【问题讨论】:

  • 当对应值未知时,您将如何评估公式?当您确定所有值都已知时,您应该创建一个函数来执行此公式并调用此函数
  • Python 无论如何都会这样做,它被称为short circuiting
  • 您要查找的术语称为“部分评估”。通常,对A=-1 部分评估f(AB,B,C) 将为您提供一些功能g(B,C),但正如您的示例所示,g(B,C) 不一定依赖于B 或C。有许多类似的变体。例如。 f(A,B,C)=A*B*C 可以部分评估为 A=0
  • @cateof:确实没有一般的答案。最严格的部分评估级别将在部分评估的公式上运行布尔可满足性的变体,以确定是否存在任何可能的输入,其余公式为真,然后同样为假。如果任一产生空集,则不需要评估 B。但如果这需要一周时间呢? B到底有多难计算?这将远远超出评论甚至 StackOverflow 问题的范围,无法完全解决。您甚至可以计算 B 同时确定是否重要。
  • 这里的大多数答案实际上并没有向您展示如何根据标准的从左到右的短路行为仅在需要时一起使用 Python 和 C 来计算 B。我更新了我的答案来做到这一点。

标签: python c++ lua lazy-evaluation


【解决方案1】:

我不知道有任何现有的可用库来处理这个问题。

通常的方法是构建一个表达式树并评估可能的结果 - 类似于编译器中的常量折叠: https://en.wikipedia.org/wiki/Constant_folding

其中一个重要方面是了解变量的允许值,从而了解允许的部分评估,例如如果x 是整数或有限浮点数,则x*0(和0*x)是0,但如果x 是IEEE 浮点数(因为它可能是Nan 或无穷大),则无法计算,或者如果x 可能是矩阵,因为[1,1]*0[0,0] 而不是标量0

【讨论】:

    【解决方案2】:

    一种方法是将表达式解析为树并评估树。所有变量都已知的子表达式将被完全评估。效果将是简化树。

    在您的示例中,树的顶部有 && 和两个子树,左侧是 A>0 的树。为了评估树,我们评估左子树,它返回 -1,因此我们不需要评估右子树,因为运算符是 &&。整棵树的计算结果为 false

    【讨论】:

    • 这是一个最小的方法。树简化意味着用false 替换false && X,用X 替换true&&X,用X 替换X && X。但这只是逻辑与。考虑A<5 && A<10。正确的简化是A<5。您可以看到,仅使用一个变量和一些常量和比较,这很快就会变得非常困难。一般问题是 NP-hard。
    • @MSalters “NP-hard”并不意味着“无法解决”。他不会在这里使用 3 页的表达方式。
    【解决方案3】:

    我不完全了解您想做什么或想了解什么,但我对 ivan_pozdeev 关于短路评估惰性评估的看法很好。

    布尔表达式从左到右计算,当结果已知时,计算停止并忽略右边的内容。

    使用 Python:

    E = "(A > 0) and (B > 5 or C > 10)"
    A = -1
    print(eval(E))
    

    给予

    False
    

    但是

    E = "(A > 0) and (B > 5 or C > 10)"
    A = 1
    print(eval(E))
    

    给出错误“name 'B' is not defined”。

    【讨论】:

      【解决方案4】:

      所以根据我对你的问题的理解,你想要类似的东西

      if (A>0) {
        B = getB();
        C = getC();
        if (B>23 || C==11)
          explode();
      }
      

      即您的表达式必须被拆分,以便您只使用已知值。

      【讨论】:

      • 是的。但是我将表达式作为“std::string”接收,创建 if-then-else 语句可能很困难
      【解决方案5】:

      你可以这样做:

      class LazyValues():
      
          def __init__(self):
              self._known_values = {}
      
          def __getitem__(self, var):
              try:
                  return self._known_values[var]
              except KeyError:
                  print("Evaluating %s..." % var)
                  return self._known_values.setdefault(var, eval(var))
      
      
      def lazy_eval(expr, lazy_vars):
          for var in lazy_vars:
              expr  = expr.replace(var, "lazy_values['%s']" % var)
              # will look like ((lazy_value['A']>0) && (lazy_value['B']>5 || lazy_value['C'] > 10))
      
          lazy_values = LazyValues()
          return eval(expr)
      
      
      lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=['A', 'B', 'C'])
      
      # Evaluating A...
      # ....
      # NameError: name 'A' is not defined
      
      A = -1
      lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=['A', 'B', 'C'])
      
      #Evaluating A...
      #False
      
      A = 5
      B = 6
      lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=['A', 'B', 'C'])
      
      # Evaluating A...
      # Evaluating B...
      # True
      

      稍后会有更多细节......

      【讨论】:

      • 我理解你的方法,它解决了问题,但是这里有很多极端情况。如果我们设置 B=6 而不是 A = -1,即先设置 B 而不是 A,则会发生异常。不会总是先拿A,再拿B,再拿C。
      • @cateof 是的,如果需要,必须事先定义 B。我的想法是,通过控制评估步骤,您可以在需要时且仅在需要时触发某些变量的计算,如果它们的计算成本很高,这很有趣。我明天应该有时间完成它... :)
      【解决方案6】:

      在我看来答案是肯定的,是的,您可以尝试评估缺少信息的表达式。您需要定义当符号查找失败时会发生什么。

      在您的情况下,您将需要一个布尔表达式求值器和一个符号表,以便求值器可以查找符号来执行表达式。

      如果您成功查找了所有符号,结果将是真或假。如果您未能查找符号,则处理这种情况,可能返回 None、nullptr 或引发/抛出异常。

      我相信你可以在你的c++程序中嵌入python解释器并调用一个函数来评估表达式,更重要的是,你可以给它一个dict来用作符号表。如果调用返回结果,则它能够找到足够的符号或结果的快捷方式,否则它将引发您的 c++ 代码可以检测到的异常。

      您可以在 python 中对函数进行原型设计,如果该方法按您想要的方式工作,则进行评估,然后嵌入。

      或者您可以使用 C++ 完成所有操作,使用语法、词法分析器、解析器和电梯。

      【讨论】:

        【解决方案7】:

        虽然这是您解决方案的一个非常粗略的实现,但它非常适合您的情况,尽管使用了很多 if else 和异常处理。

        def main_func():
            def checker(a, b=None, c=None):
                if a is None:
                    del a
                if b is None:
                    del b
                if c is None:
                    del c
                d = eval('a>0 and (b>5 or c>10)')
                return d
            return checker
        
        def doer(a=None, b=None, c=None):
            try:
                return main_func()(a,b,c)
            except NameError as e:
                if "'a' is not" in str(e):
                    return 'a'
                elif "'b' is not" in str(e):
                    return 'b'
                elif "'c' is not" in str(e):
                    return 'c'
        
        def check_ret(ret):
            return type(ret) == bool
        
        def actual_evaluator():
            getter = {
                "a": get_a,
                "b": get_b,
                "c": get_c
            }
            args = []
            while True:
                ret = doer(*tuple(args))
                if not check_ret(ret):
                    val = getter[ret]()
                    args.append(val)
                else:
                    return ret
        
        if __name__ == '__main__':
                print actual_evaluator()
        

        现在解释我的代码,main_func 返回另一个函数,用于计算字符串中的给定表达式。虽然这里的字符串已经过硬编码,但您始终可以将其作为参数传递给函数,并将eval 中的字符串替换为参数。

        doer中,main_func返回的函数被调用,如果NameError被抛出,这发生在前面的条件为假并且要计算新值的情况下,然后它返回需要的特定变量要计算。所有这些都在actual_evaluator 中进行检查,其中变量的值是通过某个函数get_variable_name 获取的,您可以在getter 字典中定义该函数。在我的代码中,我使用随机数来检查有效性,但就像你说的那样,你必须通过其他方式评估各种变量,以便调用相应的函数。

        【讨论】:

        • 这基本上归结为eval。正如我在回答中指出的那样,eval 遭受严格的从左到右评估,这使得表达式的写入顺序很重要。
        • 确实如此,但大多数语言会从左到右进行评估。即使在数学上,表达式也会从左到右进行评估(我认为),所以我想不出有人会在不评估 A 的情况下尝试寻找 B 的场景。
        【解决方案8】:

        由于短路行为,即使没有定义所有包含的值(如果可能),Python 也可以评估表达式。如果不是,则会引发异常:

        In [1]: a= False
        
        In [3]: a and b
        Out[3]: False
        
        In [4]: a or b
        NameError: name 'b' is not defined
        

        但表达式是从左到右计算的:

        In [5]: b and a
        NameError: name 'b' is not defined
        

        【讨论】:

          【解决方案9】:

          我过去曾为此做过“自己动手”的方法。简单的事情并不难;您只需创建自己的对象来实现魔术数学方法并跟踪其他对象。

          如果您需要更全面的功能,sympy 项目旨在进行符号数学...

          【讨论】:

            【解决方案10】:

            我会看看 sympy 或其他计算机代数系统。我相信 pxeression 加上短路评估的代数简化将使您能够评估所有可能获得结果的情况。在某些情况下,您需要知道某个变量的值。例如,如果您有一个像 a == b 这样的简单表达式,那么您将不会在不知道 a 和 b 的值的情况下取得任何进展。然而,像 (a >= 0) ||(a

            【解决方案11】:

            听起来你有两个挑战:

            1. 计算某些变量值的成本很高,因此您要避免计算不需要计算表达式的值;和
            2. 您的表达式以字符串形式存在,在运行时组成,因此您不能使用 C++ 的内置短路逻辑。

            这意味着您需要某种方法在运行时评估表达式,并且如果可能,您希望利用短路逻辑。 Python 可能是一个不错的选择,如下例所示。

            有一个简短的 Python 脚本 (evaluate.py),它定义了一个可以从 C 或 C++ 程序调用的 evaluate() 函数。 evaluate() 函数将尝试计算您给它的表达式(如果需要,将“&&”和“||”转换为“and”和“or”)。如果它需要一个尚未定义的变量,它将通过调用 C/C++ 程序中定义的 get_var_value() 函数来检索该变量的值(然后缓存该值以供以后使用)。

            这种方法将使用正常的短路行为,因此它只会请求完成评估表达式所需的变量值。请注意,这不会重新排列表达式以选择评估它所需的最小变量集;它只是使用标准的短路行为。

            更新:我在末尾添加了一个示例,该示例使用 .cpp 文件中的多行字符串文字来定义 Python 脚本。如果您不想与可执行文件一起安装单独的 evaluate.py 文件,这可能很有用。它还稍微简化了 Python 初始化。

            以下脚本中的 C/Python 交互基于 https://docs.python.org/2/extending/embedding.htmlhttps://docs.python.org/2/c-api/arg.html 中的代码。

            这里是文件:

            evaluate.py(Python 脚本)

            # load embedded_methods module defined by the parent C program
            from embedded_methods import get_var_value
            
            # define a custom dictionary class that calls get_var_value(key) for any missing keys.
            class var_dict(dict):
                def __missing__(self, var):
                    self[var] = val = get_var_value(var)
                    return val
            
            # define a function which can be called by the parent C program
            def evaluate(expr):
                # Create a dictionary to use as a namespace for the evaluation (this version 
                # will automatically request missing variables).
                # Move this line up to the module level to retain values between calls.
                namespace = var_dict()
            
                # convert C-style Boolean operators to Python-style
                py_expr = expr.replace("||", " or ").replace("&&", " and ").replace("  ", " ")
            
                print('evaluating expression "{}" as "{}"'.format(expr, py_expr))
            
                # evaluate the expression, retrieving variable values as needed
                return eval(py_expr, namespace)
            

            evaluate.c(你的主程序;也可以是evaluate.cpp,用g++编译)

            // on Mac, compile with gcc -o evaluate evaluate.c -framework Python
            #include <Python/Python.h>  // Mac
            // #include <Python.h> // non-Mac?
            
            // retain values of argc and argv for equation evaluation
            int argc;
            char **argv;
            
            /* 
               Calculate the value of a named variable; this is called from the Python 
               script to obtain any values needed to evaluate the expression. 
            */
            static PyObject* c_get_var_value(PyObject *self, PyObject *args)
            {
                int var_num;
                char *var_name;
                char err_string[100];
                long var_value;
                if(!PyArg_ParseTuple(args, "s:get_var_value", &var_name)) {
                    PyErr_SetString(PyExc_ValueError, "Invalid arguments passed to get_var_value()");
                    return NULL;
                }
                // change the code below to define your variable values
                // This version just assumes A, B, C are given by argv[2], argv[3], argv[4], etc.
                printf("looking up value of %s: ", var_name);
                var_num = var_name[0]-'A';
                if (strlen(var_name) != 1 || var_num < 0 || var_num >= argc-2) {
                    printf("%s\n", "unknown");
                    snprintf(
                        err_string, sizeof(err_string), 
                        "Value requested for unknown variable \"%s\"", var_name
                    );
                    PyErr_SetString(PyExc_ValueError, err_string);
                    return NULL;  // will raise exception in Python
                } else {
                    var_value = atoi(argv[2+var_num]);
                    printf("%ld\n", var_value);
                    return Py_BuildValue("l", var_value);
                }
            }
            
            // list of methods to be added to the "embedded_methods" module
            static PyMethodDef c_methods[] = {
                {"get_var_value", c_get_var_value, METH_VARARGS, // could use METH_O
                 "Retrieve the value for the specified variable."},
                {NULL, NULL, 0, NULL} // sentinel for end of list
            };
            
            int main(int ac, char *av[])
            {
                PyObject *p_module, *p_evaluate, *p_args, *p_result;
                long result;
                const char* expr;
            
                // cache and evaluate arguments
                argc = ac;
                argv = av;
                if (argc < 2) {
                    fprintf(
                        stderr, 
                        "Usage: %s \"expr\" A B C ...\n"
                        "e.g.,  %s \"((A>0) && (B>5 || C > 10))\" 10 9 -1\n", 
                        argv[0], argv[0]
                    );
                    return 1;
                }
                expr = argv[1];
            
                // initialize Python
                Py_SetProgramName(argv[0]);
                Py_Initialize();
                // Set system path to include the directory where this executable is stored
                // (to find evaluate.py later)
                PySys_SetArgv(argc, argv);
            
                // attach custom module with get_var_value() function
                Py_InitModule("embedded_methods", c_methods);
            
                // Load evaluate.py
                p_module = PyImport_ImportModule("evaluate");
                if (PyErr_Occurred()) { PyErr_Print(); }
                if (p_module == NULL) {
                    fprintf(stderr, "unable to load evaluate.py\n");
                    return 1;
                }
            
                // get a reference to the evaluate() function
                p_evaluate = PyObject_GetAttrString(p_module, "evaluate");
                if (!(p_evaluate && PyCallable_Check(p_evaluate))) {
                    fprintf(stderr, "Cannot retrieve evaluate() function from evaluate.py module\n");
                    return 1;
                }
            
                 /*
                    Call the Python evaluate() function with the expression to be evaluated.
                    The evaluate() function will call c_get_var_value() to obtain any
                    variable values needed to evaluate the expression. It will use 
                    caching and normal logical short-circuiting to reduce the number 
                    of requests.
                 */
                p_args = Py_BuildValue("(s)", expr);
                p_result = PyObject_CallObject(p_evaluate, p_args);
                Py_DECREF(p_args);
                if (PyErr_Occurred()) {
                    PyErr_Print();
                    return 1;
                }
                result = PyInt_AsLong(p_result);
                Py_DECREF(p_result);
            
                printf("result was %ld\n", result);
            
                Py_DECREF(p_evaluate);
                Py_DECREF(p_module);
                return 0;
            }
            

            结果:

            $ evaluate "((A>0) && (B>5 || C > 10))" -1 9 -1
            evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))"
            looking up value of A: -1
            result was 0
            
            $ evaluate "((A>0) && (B>5 || C > 10))" 10 9 -1
            evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))"
            looking up value of A: 10
            looking up value of B: 9
            result was 1
            
            $ evaluate "((A>0) && (B>5 || C > 10))" 10 3 -1
            evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))"
            looking up value of A: 10
            looking up value of B: 3
            looking up value of C: -1
            result was 0
            

            作为替代方案,您可以将所有这些代码合并到一个 .cpp 文件中,如下所示。这使用了 C++11 中的多行字符串文字功能。

            自包含的evaluate.cpp

            // on Mac, compile with g++ evaluate.cpp -o evaluate -std=c++11 -framework Python
            #include <Python/Python.h>  // Mac
            //#include <Python.h> // non-Mac?
            
            /* 
               Python script to be run in embedded interpreter.
               This defines an evaluate(expr) function which will interpret an expression
               and return the result. If any variable values are needed, it will call the
               get_var_values(var) function defined in the parent C++ program
            */
            const char* py_script = R"(
            # load embedded_methods module defined by the parent C program
            from embedded_methods import get_var_value
            
            # define a custom dictionary class that calls get_var_value(key) for any missing keys.
            class var_dict(dict):
                def __missing__(self, var):
                    self[var] = val = get_var_value(var)
                    return val
            
            # define a function which can be called by the parent C program
            def evaluate(expr):
                # Create a dictionary to use as a namespace for the evaluation (this version 
                # will automatically request missing variables).
                # Move this line up to the module level to retain values between calls.
                namespace = var_dict()
            
                # convert C-style Boolean operators to Python-style
                py_expr = expr.replace("||", " or ").replace("&&", " and ").replace("  ", " ")
            
                print('evaluating expression "{}" as "{}"'.format(expr, py_expr))
            
                # evaluate the expression, retrieving variable values as needed
                return eval(py_expr, namespace)
            )";
            
            // retain values of argc and argv for equation evaluation
            int argc;
            char **argv;
            
            /* 
               Calculate the value of a named variable; this is called from the Python 
               script to obtain any values needed to evaluate the expression. 
            */
            static PyObject* c_get_var_value(PyObject *self, PyObject *args)
            {
                int var_num;
                char *var_name;
                char err_string[100];
                long var_value;
                if(!PyArg_ParseTuple(args, "s:get_var_value", &var_name)) {
                    PyErr_SetString(PyExc_ValueError, "Invalid arguments passed to get_var_value()");
                    return NULL;
                }
                // change the code below to define your variable values
                // This version just assumes A, B, C are given by argv[2], argv[3], argv[4], etc.
                printf("looking up value of %s: ", var_name);
                var_num = var_name[0]-'A';
                if (strlen(var_name) != 1 || var_num < 0 || var_num >= argc-2) {
                    printf("%s\n", "unknown");
                    snprintf(
                        err_string, sizeof(err_string), 
                        "Value requested for unknown variable \"%s\"", var_name
                    );
                    PyErr_SetString(PyExc_ValueError, err_string);
                    return NULL;  // will raise exception in Python
                } else {
                    var_value = atoi(argv[2+var_num]);
                    printf("%ld\n", var_value);
                    return Py_BuildValue("l", var_value);
                }
            }
            
            // list of methods to be added to the "embedded_methods" module
            static PyMethodDef c_methods[] = {
                {"get_var_value", c_get_var_value, METH_VARARGS, // could use METH_O
                 "Retrieve the value for the specified variable."},
                {NULL, NULL, 0, NULL} // sentinel for end of list
            };
            
            int main(int ac, char *av[])
            {
                PyObject *p_module, *p_evaluate, *p_args, *p_result;
                long result;
                const char* expr;
            
                // cache and evaluate arguments
                argc = ac;
                argv = av;
                if (argc < 2) {
                    fprintf(
                        stderr, 
                        "Usage: %s \"expr\" A B C ...\n"
                        "e.g.,  %s \"((A>0) && (B>5 || C > 10))\" 10 9 -1\n", 
                        argv[0], argv[0]
                    );
                    return 1;
                }
                expr = argv[1];
            
                // initialize Python
                Py_SetProgramName(argv[0]);
                Py_Initialize();
            
                // attach custom module with get_var_value() function
                Py_InitModule("embedded_methods", c_methods);
            
                // run script to define evalute() function
                PyRun_SimpleString(py_script);
                if (PyErr_Occurred()) {
                    PyErr_Print(); 
                    fprintf(stderr, "%s\n", "unable to run Python script");
                    return 1;
                }
            
                // get a reference to the Python evaluate() function (can be reused later)
                // (note: PyRun_SimpleString creates objects in the __main__ module)
                p_module = PyImport_AddModule("__main__");
                p_evaluate = PyObject_GetAttrString(p_module, "evaluate");
                if (!(p_evaluate && PyCallable_Check(p_evaluate))) {
                    fprintf(stderr, "%s\n", "Cannot retrieve evaluate() function from __main__ module");
                    return 1;
                }
            
                /*
                   Call the Python evaluate() function with the expression to be evaluated.
                   The evaluate() function will call c_get_var_value() to obtain any
                   variable values needed to evaluate the expression. It will use 
                   caching and normal logical short-circuiting to reduce the number 
                   of requests.
                */
                p_args = Py_BuildValue("(s)", expr);
                p_result = PyObject_CallObject(p_evaluate, p_args);
                Py_DECREF(p_args);
                if (PyErr_Occurred()) {
                    PyErr_Print();
                    return 1;
                }
                result = PyInt_AsLong(p_result);
                Py_DECREF(p_result);
            
                printf("result was %ld\n", result);
            
                Py_DECREF(p_module);
                Py_DECREF(p_evaluate);
                return 0;
            }
            

            【讨论】:

            • 其他 2 个答案的副本。
            • @ivan_pozdeev 我不知道你指的是哪一个。无论如何,我现在已经使它更加完整,使用 C 接口和逻辑来逐步评估表达式,而不仅仅是指示它是否可以被评估。
            • 更新:我已经放弃了基于NameError 异常查找变量值的方法,现在使用可以在需要时自动请求和缓存缺失值的命名空间。我认为这是一个更清洁的解决方案。
            【解决方案12】:

            我从这个问题假设

            1. 你有一个依赖于各种函数结果的逻辑表达式;
            2. 您多次使用其中一些函数的值(可能在计算此表达式之前,或可能在此表达式中),因此您希望存储它们的结果以避免调用它们两次;和
            3. 您希望计算逻辑表达式,并且在此过程中,您希望检索和存储以前未运行过的函数的值,但它们的值仅足以计算表达式(使用正常的短路行为)。

            我在另一个答案中提到,您最好只使用 C++ 中的内置短路行为。为此并实现目标 2,您需要在逻辑表达式中使用函数而不是变量。这样,您可以在表达式需要时触发缺失值的计算。

            以下是执行此操作的两种方法。第一个使用通用缓存包装器包装您的慢速函数。第二个为每个慢速函数定义了一个自定义的缓存助手。编译后,应使用您的 A、B 和 C 值调用其中任何一个进行测试,例如evaluate_cached 10 9 -1。他们都会按照你想要的方式行事。

            evaluate_cached.cpp

            # include <stdio.h>
            # include <stdlib.h>
            # include <unordered_map>
            
            static char **args;
            
            // define (slow) functions to calculate each of the needed values
            int A() {
              printf("Calculating value for A\n");
              return atoi(args[1]);
            }
            
            int B() {
              printf("Calculating value for B\n");
              return atoi(args[2]);
            }
            
            int C() {
              printf("Calculating value for C\n");
              return atoi(args[3]);
            }
            
            typedef int (*int_func)(void);
            
            // wrapper to cache results of other functions
            int cached(int_func func) {
                // Create an unordered_map to hold function results
                static std::unordered_map<int_func, int> results;
            
                if (results.find(func) == results.end()) {
                    // function hasn't been called before; call and cache results
                    results[func] = func();
                }
                return results[func];
            }
            
            int main(int argc, char *argv[])
            {
                if (argc!=4) {
                    fprintf(stderr, "%s must be called with 3 values for A, B and C.\n", argv[0]);
                    return 1;
                } else {
                    args = argv;
                }
                // do the evaluation, with short-circuiting
                if (((cached(A)>0) && (cached(B)>5 || cached(C) > 10))) {
                    printf("condition was true\n");
                } else {
                    printf("condition was false\n");
                }
                return 0;
            }
            

            evaluate_helpers.c

            # include <stdio.h>
            # include <stdlib.h>
            
            static char **args;
            
            // define (slow) functions to calculate each of the needed values
            int calculate_A() {
              printf("Calculating value for A\n");
              return atoi(args[1]);
            }
            
            int calculate_B() {
              printf("Calculating value for B\n");
              return atoi(args[2]);
            }
            
            int calculate_C() {
              printf("Calculating value for C\n");
              return atoi(args[3]);
            }
            
            // define functions to retrieve values as needed,
            // with caching to avoid double-calculation
            int A() {
              static int val, set=0;
              if (!set) val=calculate_A();
              return val;
            }
            int B() {
              static int val, set=0;
              if (!set) val=calculate_B();
              return val;
            }
            int C() {
              static int val, set=0;
              if (!set) val=calculate_B();
              return val;
            }
            
            int main(int argc, char *argv[])
            {
                if (argc!=4) {
                    fprintf(stderr, "%s must be called with 3 values for A, B and C.\n", argv[0]);
                    return 1;
                } else {
                    args = argv;
                }
                // do the evaluation, with short-circuiting
                if (((A()>0) && (B()>5 || C() > 10))) {
                    printf("condition was true\n");
                } else {
                    printf("condition was false\n");
                }
                return 0;
            }
            

            【讨论】:

            • 这是一个很好的方法,但是我在编译时没有 A()、B() 或 C()。规则“A>0 and B>5 or C>10”只有在运行时才知道。我不知道是否可以修改您的答案以捕捉动态行为。
            • @cateof 假设有一个 C++ 函数可以在需要时像 get_var_value("A") 一样调用吗?此外,您的表达式是否仅包含变量、数字、()&gt;&lt;&gt;=&lt;===&amp;&amp;||,或者可以在那里是其他条款吗?你是只处理整数,还是浮点值(在表达式和变量中)?要在 C++ 中执行此操作,您需要解析表达式,但如果只有几个可能的符号,这可能不会太难。
            • 在尝试编写自定义解析器之后,我对这种方法不那么乐观了。有很多需要担心 - 语法错误、运算符优先级、像 - 这样的一元运算符、数字的科学记数法等。Python 方法非常简单且强大。或者,您可以使用 C 解析库。 “C 表达式解析器”或“C 表达式求值器”是很好的搜索词。您需要让解析器调用变量的值查找函数,而不是使用预先提供的变量值,并且您可能需要添加短路行为。不知道这有多容易。
            • 我认为使用嵌入式解释器是可行的方法。
            猜你喜欢
            • 2016-10-09
            • 1970-01-01
            • 1970-01-01
            • 2016-02-26
            • 2018-02-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多