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

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


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

多叉樹的節點類型

node繼承體系

表達式的值,通常將其轉化成二叉樹結構,根節點表示操做符,子節點表示操做符所使用到的份量。
好比上面的表達式表示成二叉樹如圖所示:
算法

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

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

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

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

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

  • FunctionNode 全部的函數節點都以此類爲父節點,好比sin, max, exp等
  • ConstantNode PI, e等常量節點的父節點
  • OperateNode 加減乘除等節點的父節點

其中節點中全部的數字類型都是用BigNumber來表示。3d

用反射機制來查找全部可用的拓展節點

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

        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函數了。orm

語法分析、構建多叉樹

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

相關文章
相關標籤/搜索