听起来你有两个挑战:
- 计算某些变量值的成本很高,因此您要避免计算不需要计算表达式的值;和
- 您的表达式以字符串形式存在,在运行时组成,因此您不能使用 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.html 和 https://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;
}