数学解析的基于堆栈的表达式评估的效率

时间:2010-02-10 19:39:36

标签: math parsing performance rpn shunting-yard

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

我选择编写解析器的方法是使用Shunting-Yard算法转换RPN中的表达式,将原始函数(如“cos”)视为一元运算符。这意味着上面写的函数将被转换为一系列令牌,如:

1, x, cos, ln, 5, *,3, ^, exp, -

问题在于绘制我必须评估它的函数很多次,因此对每个输入值应用堆栈评估算法将是非常低效的。 我怎么解决这个问题?我是否必须忘记RPN的想法?

9 个答案:

答案 0 :(得分:3)

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

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

您是否尝试过测量代码的效果?

(抱歉,问题开始了!)

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

1)解析树木。

您可以创建一个解析树。然后执行大多数编译器所做的工作来优化表达式,常量折叠,公共子表达式消除(可以通过将常用表达式子树链接在一起并缓存结果来实现)等。

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

    *
   / \
  A   B

其中A的计算结果为0,你可以完全避免评估B,因为你知道结果为0.使用RPN你会失去懒惰的评估。

2)插值

假设您的功能是连续的,您可以使用Polynomial Interpolation以高精度逼近您的功能。通过这种方式,您可以对函数进行几次复杂的计算(根据您选择的多项式的次数),然后在其余时间进行快速多项式计算。

要创建初始数据集,您可以使用方法1或只是坚持使用您的RPN,因为您只会生成一些值。

因此,如果你使用Interpolation,你可以保留你的RPN ......

希望有所帮助!

答案 1 :(得分:2)

为什么重新发明轮子?请改用快速脚本语言。 将lua之类的东西集成到你的代码中将花费很少的时间并且非常快。

您通常可以对表达式进行字节编译,这样可以使代码运行得非常快,对于简单的一维图形来说肯定足够快。

我推荐lua作为它的快速,并且比其他任何脚本语言更容易与C / C ++集成。另一个不错的选择是python,但是虽然它更为人所知我发现整合起来比较棘手。

答案 2 :(得分:1)

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

这样你就可以解析表达式一次,保留解析树,接受一组输入,并应用解析树进行评估。

如果你担心评估步骤的性能方面(与解析步骤相比),我认为你做得更好,除非你进入矢量化(在输入向量上应用你的解析树)立即)或将操作硬编码为固定功能。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

迈克尔安德森建议Lua。如果您想尝试Lua来完成此任务,请参阅我的ae库。

答案 5 :(得分:0)

在什么意义上效率不高?有机器时间和程序员时间。是否有一个标准,它需要以特定的复杂程度运行多快?完成任务并转到下一个任务更重要(完美主义者有时候永远不会完成)?

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

我的意思是,如果你在gnuplot或Mathematica中绘制一个函数,你会怎么想?

答案 6 :(得分:0)

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

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

  • 符号表查找

希望您的符号表(包含x中的变量)将简短明了。

图书馆的功能很可能是你最大的时间,所以除非你的翻译写得不好,否则不会有问题。

但是,如果真的要求速度,你可以将表达式转换为C代码,然后编译并将其链接到dll中并加载它(大约需要一秒钟) )。那,加上数学函数的memoized版本,可以给你最好的表现。

P.S。对于解析,你的语法很香草,所以一个简单的递归下降解析器(关于一个代码页,O(n)与shunting-yard相同)应该可以正常工作。实际上,您可能只能在解析时计算结果(如果数学函数占用大部分时间),而不用打扰树,RPN,任何这些东西。

答案 7 :(得分:0)

我认为这个基于RPN的库可以达到目的:http://expressionoasis.vedantatree.com/

我将它与我的一个计算器项目一起使用,效果很好。它既小又简单,但可扩展。

答案 8 :(得分:0)

一种优化方法是用一组值替换堆栈,并将求值程序实现为three address mechine,其中每个操作从两个(或一个)位置加载并保存到第三个。这可能会造成非常严密的代码:

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;
      //...
    }
  }
}

从RPN到3地址的转换应该很容易,因为3地址是一种概括。