Math 基于堆栈的数学解析表达式求值的效率

Math 基于堆栈的数学解析表达式求值的效率,math,parsing,performance,rpn,shunting-yard,Math,Parsing,Performance,Rpn,Shunting Yard,出于学术目的,我必须编写一个应用程序来绘制用户输入表达式,如:f(x)=1-exp(3^(5*ln(cosx))+x) 我选择的编写解析器的方法是使用分流码算法转换RPN中的表达式,将“cos”等基本函数视为一元运算符。这意味着上面编写的函数将转换为一系列令牌,如: 1, x, cos, ln, 5, *,3, ^, exp, - 问题是,为了绘制函数,我必须对其进行多次求值,因此对每个输入值应用堆栈求值算法将非常低效。 我怎样才能解决这个问题?我必须忘记RPN思想吗?为什么不围绕解析树(我松

出于学术目的,我必须编写一个应用程序来绘制用户输入表达式,如:f(x)=1-exp(3^(5*ln(cosx))+x)

我选择的编写解析器的方法是使用分流码算法转换RPN中的表达式,将“cos”等基本函数视为一元运算符。这意味着上面编写的函数将转换为一系列令牌,如:

1, x, cos, ln, 5, *,3, ^, exp, -
问题是,为了绘制函数,我必须对其进行多次求值,因此对每个输入值应用堆栈求值算法将非常低效。
我怎样才能解决这个问题?我必须忘记RPN思想吗?

为什么不围绕解析树(我松散地使用“树”,在您的例子中,它是一系列操作),并相应地标记输入变量?(例如,对于输入x、y、z等,用0注释“x”表示第一个输入变量,用1注释“y”表示第二个输入变量,等等)

这样,您就可以一次性解析表达式,保留解析树,接收输入数组,并应用解析树进行计算


如果您担心评估步骤(相对于解析步骤)的性能方面,我认为您不会做得更好,除非您开始矢量化(立即对输入向量应用解析树)或将操作硬编码为固定函数。

在什么意义上效率低下?有机器时间和程序员时间。是否有一个标准来衡量它在特定复杂程度下的运行速度?完成任务并继续下一个任务是否更重要(完美主义者有时永远不会完成)

所有这些步骤都必须针对每个输入值进行。是的,您可以使用一种启发式方法来扫描操作列表并稍微清理一下。是的,您可以将其中的一些编译成汇编,而不是将+、*等作为高级函数调用。您可以将矢量化(先做所有的+,然后做所有的*,等等,用一个值的矢量)与一次只做一个值的整个过程进行比较。但是你需要吗

我的意思是,如果你用gnuplot或Mathematica绘制一个函数,你认为会发生什么

多少是“很多次”?一百万

可以输入什么样的函数?我们能假设它们是连续的吗

您是否尝试过测量代码的性能

(对不起,从问题开始!)

您可以尝试下面简要介绍的两种方法中的一种(或两者兼用)(可能还有更多):

1) 解析树。 您可以创建一个解析树。然后像大多数编译器那样优化表达式、常数折叠、公共子表达式消除(可以通过链接公共表达式子树并缓存结果来实现)等等

然后可以使用惰性评估技术来避免整个子树。例如,如果你有一棵树

    *
   / \
  A   B
如果A的计算结果为0,则可以完全避免计算B,因为您知道结果为0。使用RPN,您将在惰性评估中失败

2) 插值 假设你的函数是连续的,你可以用这个函数来近似你的函数到一个很高的精度。通过这种方式,您可以对函数进行几次复杂的计算(基于您选择的多项式次数),然后在其余时间进行快速多项式计算

要创建初始数据集,您可以使用方法1,也可以坚持使用RPN,因为您只会生成几个值

所以如果你使用插值,你可以保持你的RPN


希望有帮助

为什么要重新发明轮子?改用快速脚本语言。 将lua之类的东西集成到代码中只需要很少的时间,而且速度非常快

通常,您可以对表达式进行字节编译,这将导致代码运行速度非常快,对于简单的1D图来说速度肯定足够快


我推荐lua,因为它速度快,并且与C/C++的集成比任何其他脚本语言都容易。另一个好的选择是python,但尽管它更为人所知,但我发现它的集成更为棘手。

我所做的是使用分流算法来生成RPN。然后,我将RPN“编译”成一种标记形式,可以重复执行(解释性地执行),而无需重新解析表达式。

您对RPN的简单解释应该可以正常工作,尤其是因为它包含

  • 数学库函数,如
    cos
    exp
    ^
    (pow,涉及日志)

  • 符号表查找

希望您的符号表(其中包含像x这样的变量)简短而简单

库函数很可能是您最耗时的函数,因此除非您的解释器编写得很糟糕,否则它不会成为问题

然而,如果你真的需要提高速度,你可以将表达式翻译成C代码,编译并链接到动态链接库中,然后加载它(大约需要一秒钟)。再加上数学函数的记忆版本,可以为您提供最佳性能


另外,对于解析,您的语法非常普通,因此一个简单的递归下降解析器(大约一页代码,O(n)与分流场相同)应该可以正常工作。事实上,您可能只需要在解析时计算结果(如果数学函数占用了大部分时间),而不必担心解析树、RPN之类的东西。

Michael Anderson建议。如果您想尝试Lua完成此任务,请参阅我的库。

我认为此基于RPN的库可以达到以下目的:


我把它和我的一个计算器项目一起使用,效果很好。它小而简单,但可扩展。

一种优化方法是用一个值数组替换堆栈,并将求值器实现为一个函数,其中每个操作从两个(或一个)位置加载并保存到第三个位置。这可能导致代码非常紧凑:

struct Op {
  enum {
    add, sub, mul, div,
    cos, sin, tan,
   //....
  } op;
  int a, b, d;
}

void go(Op* ops, int n, float* v) {
  for(int i = 0; i < n; i++) {
    switch(ops[i].op) {
      case add: v[op[i].d] = v[op[i].a] + v[op[i].b]; break;
      case sub: v[op[i].d] = v[op[i].a] - v[op[i].b]; break;
      case mul: v[op[i].d] = v[op[i].a] * v[op[i].b]; break;
      case div: v[op[i].d] = v[op[i].a] / v[op[i].b]; break;
      //...
    }
  }
}
struct Op{
枚举{
加、分、多、分区,