基於逆波蘭表達式的公式解析器-算法和思路(一)


背景:

       近期項目須要自己完畢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:轉載請注明出處。

大哭



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM