具有編譯功能支持無限大數計算器的實現


本篇是MathAssist的第三篇,將在上篇所實現的BigNumber基礎上完成具有編譯功能支持無限大數的計算器SuperCalculator。


要想從形如 "(1.23435+sin(0.5*180/PI))*2468.2345" 字符串格式的表達式中求值,需要使用編譯原理的知識,不過在一般的《數據結構》課程中都會講解基礎的表達式求值問題,而本篇也是在數據結構課程的基礎上稍加拓展而實現。

多叉樹的節點類型

node繼承體系

表達式的值,一般將其轉化成二叉樹結構,根節點表示操作符,子節點表示操作符所使用到的分量。
比如上面的表達式表示成二叉樹如圖所示:

如圖所示,加乘除的操作數都是兩個,而sin只需要一個操作數,所以其子節點只有一個。而如果要支持不限參數個數的函數,就必須有兩個以上的節點,所以SuperCalculator中所使用的是多叉數。
先看所有節點類型的根類型Node

  • Format表示此節點的字符串表示,如果是sin節點此值即為"sin",乘法節點此值即為"*"
  • Index 表示此節點的首字符在字符串表達式中的索引,主要是用在錯誤定位上。
  • MinParameterCount 因為現在操作符子節點的個數不定,所以要定義一個最小所需的操作個數,如果小於這個數后就是不合法的表達式
  • Nexts 此節點所有的子節點。這個類型是List<Node>就是用於存儲不定個數的操作數。
  • Priority 表示節點在優先級,主要用在構建多叉樹時,優先級越高的節點這個值越大。比如數字節點是6,函數節點是5,乘除是4,加減是2
  • Value 計算此節點時最后的值。

下面再看Node及其子類的繼承體系。

直接從Node繼承的類有三個:NumberNode(純數字節點), ExpandNode(可拓展節點), CompartNode(間隔節點)
其中CompartNode,表示括號之類節點,只在詞法分析中用到,不會出現在樹形中

ExpandNode的直接子類有三個:FunctionNode(函數節點),ConstantNode(常量節點), OperateNode(操作符節點)

  • FunctionNode 所有的函數節點都以此類為父節點,比如sin, max, exp等
  • ConstantNode PI, e等常量節點的父節點
  • OperateNode 加減乘除等節點的父節點

其中節點中所有的數字類型都是用BigNumber來表示。

用反射機制來查找所有可用的拓展節點

如下所示的代碼,將項目中所有從ConstantNode,FunctionNode,OperateNode中繼承而來的子類,先實例化后再存儲到對應的List<T>中,這樣在構建多叉樹時用這些List<T>中的對象的Format進行字符串查找即可判斷對應的類型。

        internal static void Find(List<ConstantNode> constants, List<FunctionNode> functions, List<OperateNode> operates) {
            Assembly ass = Assembly.GetExecutingAssembly();
            Module[] modes = ass.GetModules();
            Type[] typs;

            foreach (Module m in modes) {
                typs = m.GetTypes();

                foreach (Type typ in typs) {

                    if (typ.IsSubclassOf(typeof(ConstantNode))) {
                        constants.Add(ass.CreateInstance(typ.FullName) as ConstantNode);
                    } else if (typ.IsSubclassOf(typeof(FunctionNode))) {
                        functions.Add(ass.CreateInstance(typ.FullName) as FunctionNode);
                    } else if (typ.IsSubclassOf(typeof(OperateNode))) {
                        operates.Add(ass.CreateInstance(typ.FullName) as OperateNode);
                    }
                }
            }
        }
ReflectWord.FInd

使用反射機制后就非常方便添加新的函數或操作符了。比如現在項目中只實現了Max函數,如果要實現Min函數,只需要添加一個MinNode類,重寫Format屬性返回"min",重寫Value屬性求出List<Node>中的最小值即可。將這個類實現后,反射機制將自動添加min函數了。

語法分析、構建多叉樹

Expression表達式類

Exression 用於接受一個字符串類型的表達式,並計算出值。其結構如圖所示:


從圖中可以看出詞法分析在Lexcial類中,語法分析在Syntax.cs類中。
其中Lexical.Analyse()函數負責將string value格式的字符串表達式轉化成List<Node>格式的表示式。然后Syntax.Analyse(List<Node> nodes)負責將中序表達式轉化成多叉樹,最后只返回一個根節點Node

詞法分析

詞法分析的過程,就是字符串的各種比較。

  1. 先過濾過空格回車,
  2. 是否為字母,那么就有可能是常量節點或者函數節點,就在之前用反射獲取的節點List<Node>中查找是否有對應的字符串
  3. 是否為數字
  4. 是否為左右括號
  5. 是否為"," ,函數中的多個參數是用逗號進行分隔。
  6. 是否為操作符節點
  7. ...

具體細節見代碼吧

語法分析

從中序從構建多叉樹的經典算法“數據結構”中都應該有講到,即先將中序轉化為后序或前序(本文轉化成后序),然后再將后序轉化成多叉樹。
Syntax.cs中有四個靜態函數:

  • Node Analyse(List<Node> nodes); 對外接口,將中序的參數轉化成多叉樹,返回根節點。
  • Node CreateSyntaxTree(List<Node> nodes, int first, int end); //nodes全部的中序結構,[first,end]表示要將中序結構中的哪一部分轉化成樹形。
  • Node CreateSyntaxTree(List<Node> after)  // 這個方法的參數已經是后序形式,沒有括號,函數的參數也合並到函數的子節點后,所以這里就是“數據結構”課程中最經典的,使用一個棧將后序轉化成樹。
  • List<Node> FindParameters(List<Node> nodes, int start, ref int end) 在函數的括號中,找到參數,這些參數用逗號分開,返回所以的參數。start指向左括號的位置,end返回函數的右括號。這個函數中可能會遞歸調用到CreateSyntaxTree(),因為參數也有可能是復雜的表示式。

下面詳細介紹 CreateSyntaxTree(List<Node> nodes, int first, int end)——

這個函數中遍歷nodes,對每個節點先判斷

  • 是否為數字節點或常量節點,如果是則添加到后序列表中。
  • 如果為左括號,則把左括號添加到棧
  • 如果為右括號,則一直彈出棧中元素,直到遇到棧中的左括號
  • 如果是函數節點,則使用下面的FindParameters函數將所有參數添加到這個節點的子節點中后,再將這個節點添加到后序
  • 如果是操作符節點,先看棧中有先元素,如果沒有元素直接把操作符放到棧中,如果有元素,則比較棧最后一個節點和此節點的優先級,如果棧中優先級高則彈出棧節點放到后序,並把此節點放入棧,否則將此節點放入棧。

上面這個過程也是數據結構中的經典算法。

得到正確的樹型后,就只需要簡單地調用Head.Value即可引爆多叉樹的求值過程(四年前本人是使用屬性,其實現在看來用函數更合理一些)。

結束語

最后提供程序的exe和全部源碼。

SuperCalculator_exe http://files.cnblogs.com/files/xiangism/SuperCalculator_exe.rar

SuperCalculator http://files.cnblogs.com/files/xiangism/SuperCalculator.rar

 

exe中在控制台直接輸入想要計算的表達式,然后回車即可看到結果。

補充:sin,pow之類的函數因為精度的需要,所以在正常函數的基礎上添加了一個表示精度的可選參數。

sin(PI) = 0.0000000000000032
sin(PI, 30) = 0.0000000000000032384627081457275276040217744770360060341499422643 44376687740626754440803176096879519813709774691978013205

pow(2.1, 2.1) = 4.74963807
pow(2.1, 2.1, 30) = 4.7496380917422417156885305942099853613159436030728012667686 29382182863206610681766137388241594625579055187057232218446673

 


免責聲明!

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



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