背景:

       近期项目须要自己完毕Excel的公式解析和求值,在Java中能够使用POI解析Excel公式然后求值。可是项目须要JS端和Java后端均须要支持公式解析,所以就须要自己写一套了。事实上公式解析器整体上并不复杂。原理使用逆波兰表达式就可了。

难点:

       1. 针对复杂的用户输入环境解析公式,须要注意公式书写不规范、大写和小写、空格等问题,甚至公式出错的推断。

       2. 须要解决函数扩展、函数运行等问题。

       3. 须要解决地址、地址范围取数,求值问题。

       4. 处理括号带来的优先级提升。

       5. 解决公式嵌套求知问题。

       6. 財务小数精度,解决採用IEEE 754标准出现的0.3 –0.2 != 0.1问题。

       7. 解决循环引用问题,也就是公式链成环问题。

 

理论和原理:

       1. 基于逆波兰表达式。将用户输入解析为后缀表达式。

       2. 抽象操作数和操作符,操作数(operand)操作符(operator)分别放入两个stack。

       3. 将函数抽象为operator。地址、地址范围抽象为operand。

 

       关于逆波兰表达式,在数据结构和编译原理中都已经讲烂了。简介下:

       逆波兰表达式是波兰逻辑学家在1929年提出的一种表达式表达方式。

我们传统的表达式操作符一般都是在两个数之间的(先仅限于二元操作符)。而逆波兰表达式的操作符在数字的后面。看以下一个简单样例就知道了:

转换前:1 + 2 – 3 * 4 
转换后:1 , 2 , + , 3 , 4 , * , -

       长处:逆波兰表达式很适合机器运行。能屏蔽掉括号对运算符的优先级的提升。

 

一般算法:

       逆波兰表达式的一般解析算法是建立在简单算术表达式上的,它是我们进行公式解析和运行的基础:

       1. 构建两个栈Operand(操作数栈)和Operator(操作符栈)。

       2.扫描给定的字符串,假设得到一个数字,则提取(扫描是一位一位的,一定要提取一个完整的数字)数字(下面用Operand取代),然后把Operand压入Operand栈中。

       3. 假设获得一个运算符(比方+或者*,以下用B取代),则须要和Operator栈栈顶元素(用A替代)比較:

              1) 假设A不存在,则把B压入Operator栈中。

              2)假设B是一个左括号,则忽略A和B的优先级比較,把B压入Operator栈。

              3)假设B是一个右括号。则把Operator栈顺序出栈,然后把弹出的元素顺序压入Operand栈中,直到栈顶弹出的是左括号,括号不入Operand栈中。

              4)假设A是左括号。则把B直接压入Operator栈。

              5)假设B优先级比較A高。则把B直接压入Operator栈。

              6)假设B优先级低于或等于A的优先级。则把A出栈然后压入Operand栈,重复进行此步骤直到栈顶优先级高于B的优先级或者栈顶是一个括号。

       4.扫描完成后。把Operator栈的元素依次出栈,然后依次压入Operand栈中。

 

算法特点:

       1.使用两个stack,用来构建后缀表达式,Operator栈忽略括号的情况下,始终是高优先级的Operator在栈顶。

       2.括号的优先级最低,优先级优自定义。

       3.到最后我们仅仅剩下一个Operator栈,从栈低依次运算就可以。

       4.优先级比較是关键,优先级关系到出入栈顺序和终于结果。

 

公式解析的复杂性:

       相比传统的算术表达式的解析,Excel类公式的解析更加复杂:

       1. 须要支持的操作数和操作符众多:不不过数字操作数和数学操作符,还会包括比較运算符、函数、逻辑操作数。字符串操作数,地址(如Excel的A1,A2)等。

       2. 公式的组成比較复杂。扫描困难:扫描时须要提取数字(有可能是科学计数法)、单词(比方函数、地址、地址范围),还可能出现一元操作符(比方取非!。预防需求的变化,需求是最坑程序猿的)。

       3. 同一操作符代表的含义不同:操作符重载情况比較到。比方“-”。就可以能是“减号”,也可能是“符号”;再比方运行函数功能时。函数须要多少个操作数。地址范围(A1:A10)怎样运行求值。

       4. 计算精度,这是最麻烦的(Java和JS的那个坑爹的0.1问题),既要保证效率,又要保证精度(这个世界不公平啊)。


总结:

       使用逆波兰表达式解析公式,我们须要对整改算法做小小的修改。将操作数和操作符的范围提升,不只局限在算术表达式的范围内。

这就要求我们解析的时候书写正則表達式(或词法分析的办法)来提取完整的操作数、操作符,甚至依据上下文环境对操作符进行重载(比方-究竟是减法还是符号)。今天到这儿,明天说下改进算法和代码。



PS:转载请注明出处。

大哭


posted on 2017-07-09 13:00  yutingliuyl  阅读(2066)  评论(0编辑  收藏  举报