Оценка математических выражений - PullRequest
12 голосов
/ 20 июля 2009

Я ищу алгоритм, который я могу использовать для оценки математических выражений. Я видел несколько вопросов по SO, которые схожи, но ответы на них относятся к C # / Delphi или Python. Мне нужно написать алгоритм на C:)

Проблема, которую я пытаюсь решить, дана пользователем, например

3*(2*x + 1)/x

Я могу оценить выражение для любого значения x.

Какие алгоритмы доступны для этого? Если вы хотите предложить библиотеку, которая уже делает это, то я бы предпочел библиотеку C

Спасибо

Ответы [ 5 ]

18 голосов
/ 20 июля 2009

Я спросил у Google «синтаксический анализатор рекурсивного спуска» (я не виню вас за то, что вы не знали, что искать) и нашел Анализ выражений по рекурсивному спуску , который предоставляет введение в некоторые полезные методы синтаксического анализа .

Кроме того, статья в Википедии о Анализаторе рекурсивного спуска содержит довольно полный пример на языке C.

14 голосов
/ 20 июля 2009

Алгоритм, который вам здесь нужен, - это алгоритм Маневровый двор .

Это позволяет вам преобразовать выражение в фиксированном виде в Обратная польская запись , что довольно просто для программной оценки.

Алгоритм шунтирующего двора довольно сложен, но мой опыт показывает, что вы можете закодировать его так, как написано, и все работает - вам не нужно заниматься анализом.

6 голосов
/ 20 июля 2009

Альтернативой реализации собственного анализатора и средства оценки выражений может быть ссылка на библиотеку, которая предоставляет такую ​​возможность для использования. Интересным выбором будет легко встроенный язык сценариев, такой как Lua .

Очень просто настроить экземпляр интерпретатора Lua и передать ему выражения для оценки, возвращая функцию для вызова, которая оценивает выражение. Вы даже можете позволить пользователю иметь переменные ...

Обновление: LE, Простой оценщик выражений, использующий Lua

Вот схематичная реализация простого анализатора выражений на основе интерпретатора Lua. Я скомпилировал это и попробовал несколько раз, но, конечно, нельзя доверять производственному коду без некоторого внимания к обработке ошибок и так далее. Все обычные предостережения применяются здесь.

Я скомпилировал и протестировал это в Windows, используя Lua 5.1.4 из Lua для Windows . На других платформах вам нужно будет найти Lua из вашего обычного источника или с www.lua.org.

Публичный интерфейс к LE

Вот файл le.h:

/* Public API for the LE library.
 */
int le_init();
int le_loadexpr(char *expr, char **pmsg);
double le_eval(int cookie, char **pmsg);
void le_unref(int cookie);
void le_setvar(char *name, double value);
double le_getvar(char *name);

Пример кода с использованием LE

Вот файл t-le.c, демонстрирующий простое использование этой библиотеки. Он принимает единственный аргумент командной строки, загружает его как выражение и оценивает его с помощью глобальной переменной x, изменяющейся от 0.0 до 1.0 за 11 шагов:

#include <stdio.h>
#include "le.h"

int main(int argc, char **argv)
{
    int cookie;
    int i;
    char *msg = NULL;

    if (!le_init()) {
    printf("can't init LE\n");
    return 1;
    }
    if (argc<2) {
    printf("Usage: t-le \"expression\"\n");
    return 1;
    }
    cookie = le_loadexpr(argv[1], &msg);
    if (msg) {
    printf("can't load: %s\n", msg);
    free(msg);
    return 1;
    }
    printf("  x    %s\n"
       "------ --------\n", argv[1]);
    for (i=0; i<11; ++i) {
    double x = i/10.;
    double y;

    le_setvar("x",x);
    y = le_eval(cookie, &msg);
    if (msg) {
        printf("can't eval: %s\n", msg);
        free(msg);
        return 1;
    }
    printf("%6.2f %.3f\n", x,y);
    }
}

Вот некоторый вывод из t-le:

E:...>t-le "math.sin(math.pi * x)"
  x    math.sin(math.pi * x)
------ --------
  0.00 0.000
  0.10 0.309
  0.20 0.588
  0.30 0.809
  0.40 0.951
  0.50 1.000
  0.60 0.951
  0.70 0.809
  0.80 0.588
  0.90 0.309
  1.00 0.000

E:...>

Реализация LE

Вот le.c, реализующий оценщик Lua Expression:

#include <lua.h>
#include <lauxlib.h>

#include <stdlib.h>
#include <string.h>

static lua_State *L = NULL;

/* Initialize the LE library by creating a Lua state.
 *
 * The new Lua interpreter state has the "usual" standard libraries
 * open.
 */
int le_init()
{
    L = luaL_newstate();
    if (L) 
    luaL_openlibs(L);
    return !!L;
}

/* Load an expression, returning a cookie that can be used later to
 * select this expression for evaluation by le_eval(). Note that
 * le_unref() must eventually be called to free the expression.
 *
 * The cookie is a lua_ref() reference to a function that evaluates the
 * expression when called. Any variables in the expression are assumed
 * to refer to the global environment, which is _G in the interpreter.
 * A refinement might be to isolate the function envioronment from the
 * globals.
 *
 * The implementation rewrites the expr as "return "..expr so that the
 * anonymous function actually produced by lua_load() looks like:
 *
 *     function() return expr end
 *
 *
 * If there is an error and the pmsg parameter is non-NULL, the char *
 * it points to is filled with an error message. The message is
 * allocated by strdup() so the caller is responsible for freeing the
 * storage.
 * 
 * Returns a valid cookie or the constant LUA_NOREF (-2).
 */
int le_loadexpr(char *expr, char **pmsg)
{
    int err;
    char *buf;

    if (!L) {
    if (pmsg)
        *pmsg = strdup("LE library not initialized");
    return LUA_NOREF;
    }
    buf = malloc(strlen(expr)+8);
    if (!buf) {
    if (pmsg)
        *pmsg = strdup("Insufficient memory");
    return LUA_NOREF;
    }
    strcpy(buf, "return ");
    strcat(buf, expr);
    err = luaL_loadstring(L,buf);
    free(buf);
    if (err) {
    if (pmsg)
        *pmsg = strdup(lua_tostring(L,-1));
    lua_pop(L,1);
    return LUA_NOREF;
    }
    if (pmsg)
    *pmsg = NULL;
    return luaL_ref(L, LUA_REGISTRYINDEX);
}

/* Evaluate the loaded expression.
 * 
 * If there is an error and the pmsg parameter is non-NULL, the char *
 * it points to is filled with an error message. The message is
 * allocated by strdup() so the caller is responsible for freeing the
 * storage.
 * 
 * Returns the result or 0 on error.
 */
double le_eval(int cookie, char **pmsg)
{
    int err;
    double ret;

    if (!L) {
    if (pmsg)
        *pmsg = strdup("LE library not initialized");
    return 0;
    }
    lua_rawgeti(L, LUA_REGISTRYINDEX, cookie);
    err = lua_pcall(L,0,1,0);
    if (err) {
    if (pmsg)
        *pmsg = strdup(lua_tostring(L,-1));
    lua_pop(L,1);
    return 0;
    }
    if (pmsg)
    *pmsg = NULL;
    ret = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    return ret;
}


/* Free the loaded expression.
 */
void le_unref(int cookie)
{
    if (!L)
    return;
    luaL_unref(L, LUA_REGISTRYINDEX, cookie);    
}

/* Set a variable for use in an expression.
 */
void le_setvar(char *name, double value)
{
    if (!L)
    return;
    lua_pushnumber(L,value);
    lua_setglobal(L,name);
}

/* Retrieve the current value of a variable.
 */
double le_getvar(char *name)
{
    double ret;

    if (!L)
    return 0;
    lua_getglobal(L,name);
    ret = (double)lua_tonumber(L,-1);
    lua_pop(L,1);
    return ret;
}

Примечания

Приведенный выше пример состоит из 189 строк кода, включая разбрызгивание комментариев, пустые строки и демонстрацию. Неплохо для быстрого оценщика функций, который знает, как вычислять разумно произвольные выражения для одной переменной, и имеет в своем распоряжении богатую библиотеку стандартных математических функций в своем вызове.

У вас под полным языком Тьюринга есть простое расширение, позволяющее пользователю определять завершенные функции, а также оценивать простые выражения.

3 голосов
/ 05 февраля 2016

Если вы хотите предложить библиотеку, которая уже делает это, тогда я предпочел бы библиотеку C

Вы должны взглянуть на TinyExpr . Он выполняет именно то, что вам нужно, содержится в одном исходном файле ANSI C и имеет допустимую лицензию (лицензия zlib).

Вот как будет выглядеть код для решения проблемы с примером 3*(2*x + 1)/x:

/* Store variable name(s) and pointer(s). */
double x;
te_variable vars[] = {{"x", &x}};

/* Compile the expression. */
int err;
te_expr *expr = te_compile("3*(2*x + 1)/x", vars, 1, &err);

if (expr) {
    x = 7.5; /* Set x to desired value. */
    const double result = te_eval(expr); /* Evaluate it. */

    /* Set x to a different value and re-evaluate as many times as needed. */

    te_free(expr); /* Free the memory used by the compiled expression. */

} else {

    /* TinyExpr identifies right where it found an error. */
    printf("Parse error at %d\n", err);
}

Обратите внимание, что вы можете "скомпилировать" один раз, а затем многократно вычислять с различными значениями x. Для некоторых выражений это только на несколько процентов медленнее, чем собственный C-код.

0 голосов
/ 20 июля 2009

Вы можете использовать Алгоритм шунтирования , он отлично работает и позволяет вам легко разбирать функции и т. Д. На самом деле он не вычисляется, но преобразует выражение в ONP, что может быть очень легко оценено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...