表达式求值解析
2009年11月14日星期六
12:00
不论在写编译器还是写语言解析器,都需要面对一个很重要的问题,那就是如何对表达式进行求值。在计算机系统要素一书中的第十章和第十一章有对表达式求值的描述,不过太过笼统,基本没有什么参考意义,所以我就想自己写一个,估计这是程序员的本性决定的吧,优秀的纯粹追求技术的程序员估计在难题面前都有自己跃跃欲试的冲动吧,当然,我还没有成为优秀程序员,以后肯定会是的。
在我要处理的表达式中,暂时默认的优先级规则为,{函数调用},{括号操作},{乘、除、求余},{加、减法}。
设计目标:对于给定的表达式,能够转化为顺序执行的堆栈模式,例如2+3,转化为push 2;push 3;add;
问题难点:如何能够用最少的时间复杂度完成?如果每次按照优先级来扫描表达式,我相信,问题是可以解决的,但这样重复扫描会带来时间复杂度的增加,如果能一遍扫描,那就太棒了,但可能吗?
如果您以前没有自己写过表达式求解问题的代码,或对类似问题有过思考,那么请您现在停下来,先思考片刻,再继续向下阅读,相信这样您与我会有所共鸣。
解决方案:我的想法是,第一步将表达式解析成为一颗树状表达式,第二步,对树状表达式转化
例如:2*3+6
第一步转化为:
+
* 6
2 3
第二步转化为:
Push 2
Push 3
Multiply
Push 6
Add
那么现在的问题就是,这个树状的表达式是怎么个规则?如何将表达式转化为树结构表达式呢?
树状表达式的规则:
1、非二叉树,是多个分支的树状结构
2、一颗子树代表一个子表达式
3、树顶代表操作,叶子代表操作数,某个操作可能针对一个操作数,也可能是多个操作数
表达式转化为树状结构表达式的规则:
1、 遇到加、减操作,则将表达式分为两个表达式
2、如果表达式中不存在加减操作,则遇到乘、除、求余操作,分为两个表达式
3、不管何种情况遇到括号操作,括号内容为表达式
4、不管何时遇到函数操作,函数代表一个子表达式
代码流程:
1、 先看没有括号操作、函数操作时的情形处理,本质上扩号,或函数内子表达式就是加减乘除求余组合而成的表达式,所以分析没有括号操作,函数操作的表达式处理,是有着重要的意义的。
代码流程:
获取第一个子表达式,记录为A X B,A代表左操作数,B代表右操作数,X代表操作,此时建立子树,
X
A B
记录当前操作数为B,当前操作符号为X,
不断获取下一个操作符,与下一个操作数,假设此时获取操作数为C,操作符为Y,
A:如果Y的操作优先级大于等于当前操作符的优先级,则(1)利用当前操作数与新获取操作数,以及新获取操作符,生成子树;(2)将X原来当前操作数的分支去除,并将新建立子树,做为X的子树分支;(3)记录当前操作符为新获取操作符,当前操作数为新获取操作数;
建立树结构为:
X
A Y
B C
B:如果Y的操作优先级小于当前操作符的优先级,则寻找X操作符所在子树的根节点,将X所在子树做为新的子树,与新获取操作数,新获取操作符形成新的树结构;
建立的数结构为:
Y
X C
A B
在处理仅仅由加、减、乘、除、求余组合而成的表达式时,生成的数结构均为二叉树形式。
2、 对括号表达式、函数操作表达式的处理
括号表达式与函数操作表达式相对于只有加减乘除求余操作的表达式更加复杂,但其核心本质与后者无异。
当前解析标识如果为‘(’,则说明遇到了括号表达式,此时需要做的事情有两件:
(1) 提取括号内表达式,注意括号的递归;
(2) 递归解析获取的括号内表达式,形成表达式子树
括号表达式形成的表达式子树,在我们看来,它代表的仅仅为一个操作数,类似于前边操作数C,假设我们最终得到的括号表达式子树形式如下:
T
X1 T1
X2 X3
则与前边形成表达式树融合在一起,将形成如下形式:
X
A Y
B T
X1 T1
X2 X3
函数操作表达式的处理与括号表达式的处理基本一致,仅仅在扫描字元时有些区别。
程序设计:
每次动手写一个系统前都很激动,但分析清楚了问题的流程及处理方案后,编码就基本就成了苦力活了,但把这个苦力活做好,其实也不容易。
我觉得从程序系统上来说,好的系统最重要有两个标准:扩展性、扩平台性。
未来针对表达式求值这个问题可以的扩展主要是运算的添加,这个需求我们完全可以通过添加函数的方式来应对,但如果直接添加代码,虽然也是可行的,但总是觉得不怎么好,如果能够直接通过接口设置一些内容就可以实现运算符的添加,我觉得将非常棒。例如现在表达式求值模块已经做成了一个库,或者其它形式的东西,此时我们通过获取其中的接口,对其进行了设置,就能够使得原来的库识别新的操作符,或称函数。
上边想法虽好,但是实现起来还是有些麻烦,所以暂时先不考虑这些了。
程序分为三个基本模块:
1、 字元扫描模块
2、表达式树建立模块
3、生成堆栈处理代码模块
前两个模块会存在递归调用,递归入口是表达式求解函数,表达式求解函数分为几个步骤,如果其中执行过程中遇到子表达式,则递归调用表达式求解函数,生成子表达式的子树。