续:
改进的算法描述:
1.构建两个栈Operand(操作数栈)和Operator(操作符栈)和一个LAST_TOKEN标记字段。
2.扫描给定的字符串,扫描时注意跳过空格,提取完整的操作数、操作符和函数。
3.如果获得一个运算符(用B代替)首先构造响应的运算符,构造时需要比较LAST_TOKEN记录字段,来判断重载的操作符的含义,比如如果LAST_TOKEN是数字,那么“-”就是一个数学运算符,如果LAST_TOKEN是运算符,那么要看“-”后面是不是数字,如果是数字,则认为“-”是“负号”。构造好Operator后和Operator栈栈顶元素(用A替代)比较:
1)如果A不存在,则把B压入Operator栈中;
2)如果B是一个左括号,则忽略A和B的优先级比较,把B压入Operator栈。
3)如果B是逗号,同样忽略A和B的比较,把B压入Operator栈。
4)如果B是一个右括号,则把Operator栈顺序出栈,然后把弹出的元素顺序压入Operand栈中,出栈是需要判断逗号情况,如果是逗号,则不入栈,记录逗号个数,直到栈顶弹出的是左括号,括号不入Operand栈中。之后看Operator栈顶元素,如果栈顶元素是函数类型,则把函数Operator出栈然后将逗号个数+1构造数字操作数压入Operand栈中,然后把函数Operator也压入Operand栈中。
5)如果A是左括号,则把B直接压入Operator栈。
6)如果B优先级比A高,则把B直接压入Operator栈。
7)如果B优先级低于或等于A的优先级,则把A出栈然后压入Operand栈,反复进行此步骤直到栈顶优先级高于B的优先级或者栈顶是一个括号。
4.扫描完毕后,把Operator栈的元素依次出栈,然后依次压入Operand栈中。
算法特点:
(1)和原来的逆波兰表达式处理上没什么不同,但是改进的算法中,描述清了更多的计算符号。
(2)整个处理过程中,使用一个Flag来记录最近一次的符号类型,这个Flag将帮助我们判断重载的符号的符号意义,比如最近一次的符号是一个Operand那么“-”可以判断代表的是减法操作符,但是如果Flag是Operator那么“-”应该代表的是负号。
(3)处理函数时,先统一处理为UnknownFun,等函数执行的时候,我们向函数管理器请求实际执行对象,这样在解析的时候可以不用同时判断函数是否受支持,方便我们的程序扩展。
公式求值:
逆波兰表达式的公式求值就是一个简单的弹Stack操作,实现起来更简单。我们只需要注意的是,我们的运算规则因为不仅仅是算术运算,还可能有逻辑运算,所以Operator的抽象类应该规定两个抽象方法:evaluation和needArgus,每个operator实现自己的evaluation来实际执行运算规则,并返回一个Operand(父类),needArgs表示自己需要的参数格式,evaluation方法接受的就是当前的Operand 栈,这样就不用管每个Operand需要多少操作数了。evaluation可以跑出EvaluationException错误,表示执行逻辑发生问题,比如 1 / 0 的错误。
类图:
这是类图,可以参考实现,有了类图,代码就不难了(不过这个问题中,代码实现起来还真会有点小难度)。
总结:
用逆波兰表达式解析公式总体思路上不算太难。但是真正实现起来,因为用户输入的复杂性,所以对正则表达式的编写比较严格。完成中缀表达式的构建后,公式求值很简单。代码有时间再贴出来,因为一些原因,现在就不贴源代码了。不过思路大概就是上面的了。
PS:有些实现中,对于公式的嵌套解析使用了递归的算法,这样能简化代码和逻辑。但是实际使用时容易产生效率问题,公式嵌套时完全可以改为非递归的实现。公式链就比较麻烦了,需要使用一个全局的公式管理器为公式规定执行顺序,这是最简单的方案。