Ответ 1
Похоже, у вас есть две проблемы:
- Вычислить некоторые значения переменных очень дорого, поэтому вы хотите избежать вычисления значений, которые не нужны для оценки выражения; и
- Ваше выражение существует как строка, составленная во время выполнения, поэтому вы не можете использовать встроенную логику короткого замыкания С++.
Это означает, что вам нужно каким-то образом оценить выражение во время выполнения, и, если это возможно, вы бы хотели воспользоваться схемой короткого замыкания. Python может быть хорошим выбором для этого, как показано в примере ниже.
Существует короткий Python script (evaluate.py
), который определяет функцию evaluate()
, которая может быть вызвана из вашей программы на C или С++. Функция evaluate()
попытается оценить выражение, которое вы ему даете (перевод "& &" и "||" в "и" и "или" при необходимости). Если для этого требуется переменная, которая еще не определена, она будет извлекать значение для этой переменной, вызывая функцию get_var_value()
, определенную в программе C/С++ (и затем кэширует значение для последующего использования).
Этот подход будет использовать нормальное поведение короткого замыкания, поэтому он будет запрашивать только значения переменных, необходимые для завершения оценки выражения. Обратите внимание, что это не приведет к изменению выражения для выбора минимального набора переменных, необходимых для его оценки; он просто использует стандартное поведение короткого замыкания.
UPDATE: я добавил пример в конце, который определяет Python script, используя многострочный строковый литерал в файле .cpp. Это может быть полезно, если вы не хотите устанавливать отдельный файл valu.py вместе с исполняемым файлом. Это также немного упрощает инициализацию Python.
Взаимодействие C/Python в приведенных ниже сценариях основано на коде в https://docs.python.org/2/extending/embedding.html и https://docs.python.org/2/c-api/arg.html.
Вот файлы:
оцените .py (Python script)
# 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)
оцените .c (ваша основная программа, также может быть оценена .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 файл, как показано ниже. Это использует многострочный строковый литерал в С++ 11.
Автономная оценка .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;
}