【演算法】友誼與雪花的舞動,指令碼解析器原理

lanedm發表於2023-11-13

在11月的下雪天,小悅身處於溫暖的辦公室中,窗外的雪花在燈光下翩翩起舞。她盯著電腦螢幕,不經意間,一個熟悉的身影從辦公室門口處經過,吸引了她的目光。那個人看上去很像是一個女孩,名叫蘇菲,是她在大學時期遇到的國外交換生。

小悅的心跳加速,她有些不敢相信自己的眼睛。在她的記憶中,蘇菲是一個溫柔、聰明且樂於助人的女孩。她們曾經一起上過電腦科學課,蘇菲對數學和程式設計的熱愛給小悅留下了深刻的印象。在課程中,蘇菲表現出了非凡的程式設計天賦和紮實的技術功底,她的程式設計能力讓小悅敬佩不已。

小悅忍不住站起來,快步走向那個人。她輕輕地拍了拍她的肩膀,問道:“蘇菲?”

那個女孩轉過身來,露出了一張熟悉的面孔。她的眼睛閃爍著聰慧的光芒,嘴角掛著一抹微笑。她驚喜地喊道:“小悅?”

兩人緊緊地擁抱在一起,重逢的喜悅讓她們感到溫暖。她們來到樓下的咖啡廳,開始回憶起大學時期的時光,談論著彼此的生活和工作。

小悅感到非常驚喜,沒想到蘇菲會來到她的辦公樓。蘇菲也並不知道小悅在這裡工作。或許是命運的安排,讓她們再次相遇。小悅心想,一定要好好和蘇菲聊聊天,暢談彼此的近況,重溫那段美好的大學時光。

小悅想起了那次電腦科學課的上機考試。她需要實現一個計算器表示式演演算法。小悅在考試前嘗試著自己實現了一個多項式解析式演演算法,但是效果並不甚理想。這個問題讓她感到很困惑,不知道如何著手。就在這時,蘇菲主動幫助了她。

蘇菲耐心地解釋瞭如何分析問題、拆分令牌、使用逆波蘭表示式演演算法進行計算,以及如何使用棧來處理字尾表示式。她的思路清晰、有條理,讓小悅瞬間明白了問題的解決方法。

在蘇菲的幫助下,小悅成功地解決了問題,並且在考試中取得了優異的成績。她一直感激蘇菲的幫助和鼓勵。

回憶起那段經歷,小悅不禁感慨萬分。她對蘇菲說:“謝謝你,蘇菲。你的幫助和支援讓我變得更加自信和勇敢。”

蘇菲微笑著說:“我們是朋友,小悅。無論何時何地,我都會盡力幫助你。”

她們聊了很久,談論著過去的點點滴滴和現在的變化。畢業後,蘇菲選擇繼續深造,攻讀電腦科學碩博學位。在國外留學期間,她積累了豐富的經驗和技能,志向是成為一名電腦科學家。然而,儘管小悅和蘇菲在大學時期形影不離,但畢業後兩人便各奔東西,相距太遠,漸漸失去了聯絡。她們還計劃著再次回到校園,去湖心島看看那個曾經給她們帶來無限回憶的地方。

在分別前,小悅緊緊地擁抱了蘇菲一次。她心裡默默地想:謝謝你,蘇菲,謝謝你陪我度過了那段美好的時光。我會永遠珍惜這份友誼。

隨著時間的推移,雖然小悅和蘇菲的聯絡越來越少。但是她們的友誼和回憶永遠留在彼此的心中。在這個下雪的日子裡,小悅重新與蘇菲相遇,讓她回憶起了那些青蔥校園歲月和湖心島的甜蜜時光。她感到無比幸福和感激,因為她知道這些美好的回憶將永遠伴隨著她走過人生的旅程。即使未來還有更多的挑戰等待著她,小悅也相信自己的能力能夠克服一切困難。


當時小悅和蘇菲面對的考試題目為:要求計算一個包含數字和運運算元的字串的結果,包括加減乘除和冪運算,還有括號。數字可以是整數或小數,字串中可以有空格。要求處理字串中的所有運運算元和數字,並計算出最終的結果。

示例:3 * (4 +       (2 / 3) * 6 - 5)=9;

123      -( 4^ (       3 -   1) * 8 - 8      /(     1 + 1 ) *(3 -1) )=3; 


演演算法實現1:

  1 using System;
  2 using System.Collections.Generic;
  3 
  4 public static class Edm {
  5 
  6   public static double calculate(string input)
  7     {
  8         // 從輸入中移除空格
  9         input = input.Replace(" ", "");
 10 
 11         // 將輸入轉換為令牌列表
 12         List<string> tokens = new List<string>();
 13         string currentToken = "";
 14         foreach (char c in input) {
 15             if (char.IsDigit(c) || c == '.') { // 如果字元是數字或小數點
 16                 currentToken += c; // 將字元新增到當前令牌中
 17             } else {
 18                 if (currentToken != "") { // 如果當前令牌不為空
 19                     tokens.Add(currentToken); // 將當前令牌新增到令牌列表中
 20                     currentToken = ""; // 重置當前令牌
 21                 }
 22                 tokens.Add(c.ToString()); // 將字元轉換為字串並新增到令牌列表中
 23             }
 24         }
 25         if (currentToken != "") { // 如果當前令牌不為空
 26             tokens.Add(currentToken); // 將當前令牌新增到令牌列表中
 27         }
 28 
 29         // 使用逆波蘭演演算法評估令牌
 30         Stack<string> operatorStack = new Stack<string>(); // 運運算元棧
 31         Queue<string> outputQueue = new Queue<string>(); // 輸出佇列
 32         foreach (string token in tokens) {
 33             if (double.TryParse(token, out double num)) { // 如果令牌可以轉換為雙精度浮點數
 34                 outputQueue.Enqueue(num.ToString()); // 將數字轉換為字串並新增到輸出佇列中
 35             } else if (IsOperator(token)) { // 如果令牌是運運算元
 36                 while (operatorStack.Count > 0 && IsOperator(operatorStack.Peek()) && GetPrecedence(token) <= GetPrecedence(operatorStack.Peek())) {
 37                     outputQueue.Enqueue(operatorStack.Pop()); // 將運運算元棧中的運運算元彈出並新增到輸出佇列中
 38                 }
 39                 operatorStack.Push(token); // 將運運算元新增到運運算元棧中
 40             } else if (token == "(") { // 如果令牌是左括號
 41                 operatorStack.Push(token); // 將左括號新增到運運算元棧中
 42             } else if (token == ")") { // 如果令牌是右括號
 43                 while (operatorStack.Count > 0 && operatorStack.Peek() != "(") {
 44                     outputQueue.Enqueue(operatorStack.Pop()); // 將運運算元棧中的運運算元彈出並新增到輸出佇列中
 45                 }
 46                 if (operatorStack.Count == 0) {
 47                     throw new Exception("Mismatched parentheses"); // 丟擲異常,提示括號不匹配
 48                 }
 49                 operatorStack.Pop(); // 彈出左括號
 50             } else {
 51                 throw new Exception("Invalid token: " + token); // 丟擲異常,提示令牌無效
 52             }
 53         }
 54         while (operatorStack.Count > 0) {
 55             if (operatorStack.Peek() == "(") {
 56                 throw new Exception("Mismatched parentheses"); // 丟擲異常,提示括號不匹配
 57             }
 58             outputQueue.Enqueue(operatorStack.Pop()); // 將運運算元棧中的運運算元彈出並新增到輸出佇列中
 59         }
 60 
 61         // 評估字尾表示式
 62         Stack<double> operandStack = new Stack<double>(); // 運算元棧
 63         foreach (string token in outputQueue) {
 64             if (double.TryParse(token, out double num)) { // 如果令牌可以轉換為雙精度浮點數
 65                 operandStack.Push(num); // 將數字新增到運算元棧中
 66             } else if (IsOperator(token)) { // 如果令牌是運運算元
 67                 double b = operandStack.Pop();
 68                 double a = operandStack.Pop();
 69                 double result = EvaluateOperator(token, a, b); // 計算運運算元對應的結果
 70                 operandStack.Push(result); // 將計算結果壓入運算元棧中
 71             } else {
 72                 throw new Exception("Invalid token: " + token); // 丟擲異常,提示令牌無效
 73             }
 74         }
 75         if (operandStack.Count != 1) {
 76             throw new Exception("Invalid expression"); // 丟擲異常,提示表示式無效
 77         }
 78         return operandStack.Pop(); // 返回運算元棧中唯一的元素作為計算結果
 79     }
 80 
 81     static bool IsOperator(string token) {
 82         return token == "+" || token == "-" || token == "*" || token == "/" || token == "^"; // 判斷一個字串是否為運運算元(+、-、*、/、^)
 83     }
 84     
 85     static int GetPrecedence(string op) {// 獲取運運算元優先順序
 86         if (op == "^") {
 87             return 3;
 88         } else if (op == "*" || op == "/") {
 89             return 2;
 90         } else if (op == "+" || op == "-") {
 91             return 1;
 92         } else {
 93             return 0;
 94         }
 95     }
 96 
 97    // 計算兩個運算元和一個運運算元的結果
 98     static double EvaluateOperator(string op, double a, double b) {
 99         if (op == "+") {
100             return a + b;
101         } else if (op == "-") {
102             return a - b;
103         } else if (op == "*") {
104             return a * b;
105         } else if (op == "/") {
106             return a / b;
107         } else if (op == "^") {
108             return Math.Pow(a, b);
109         } else {
110             throw new Exception("Invalid operator: " + op);
111         }
112     }
113 }

這段程式碼實現了一個表示式計算器,能夠接受包含基本數學運算的字串表示式,並返回計算結果。

  1. 匯入了System和System.Collections.Generic名稱空間,用於使用標準的資料結構和功能。

  2. 定義了一個靜態類Edm,其中包含一個名為calculate的靜態方法,該方法接受一個字串輸入並返回一個雙精度浮點數。

  3. calculate方法首先去除輸入中的空格,然後將輸入轉換為一個令牌列表(tokens)。

  4. 使用逆波蘭演演算法(shunting yard algorithm)對令牌進行評估。它使用了運運算元棧(operatorStack)和輸出佇列(outputQueue)來對錶達式進行處理。

  5. 在這段程式碼中,我們使用了運運算元棧和輸出佇列來處理給定的令牌(tokens)。

    1. 首先,我們遍歷令牌陣列,對每個令牌進行處理。
    2. 如果令牌是一個數字,我們將其轉換為字串並新增到輸出佇列中。
    3. 如果令牌是一個運運算元,我們需要根據運運算元的優先順序來決定其在輸出佇列中的位置。我們將當前運運算元與運運算元棧頂的運運算元進行比較,如果當前運運算元的優先順序小於或等於棧頂運運算元的優先順序,則將棧頂運運算元彈出並新增到輸出佇列中,直到滿足條件為止,然後將當前運運算元壓入運運算元棧中。
    4. 如果令牌是左括號,我們直接將其壓入運運算元棧中。
    5. 如果令牌是右括號,我們需要將運運算元棧中的運運算元彈出並新增到輸出佇列中,直到遇到左括號為止。如果在彈出運運算元時,運運算元棧為空,或者沒有遇到左括號,就會丟擲異常提示括號不匹配。
    6. 如果令牌不是數字、運運算元、左括號或右括號,則丟擲異常提示令牌無效。
    7. 最後,處理完所有令牌後,將運運算元棧中剩餘的運運算元依次彈出並新增到輸出佇列中。如果在此過程中遇到左括號,也會丟擲異常提示括號不匹配。

    這樣,我們可以將中綴表示式轉換為字尾表示式,並且在這個過程中處理了運運算元的優先順序和括號的匹配關係。

  6. 對字尾表示式進行評估。這部分程式碼使用了運算元棧(operandStack)來計算字尾表示式的值。

  7. 評估字尾表示式的詳細解釋:

    1. 建立一個空的運算元棧,用於儲存運算元。
    2. 遍歷字尾表示式中的每個令牌。
    3. 如果令牌可以轉換為雙精度浮點數,則將其壓入運算元棧中。
    4. 如果令牌是運運算元,則從運算元棧中彈出兩個運算元(假設為a和b),然後根據該運運算元對這兩個運算元進行計算,得到結果。
    5. 將計算得到的結果壓入運算元棧中。
    6. 如果令牌不是數字也不是運運算元,則丟擲異常提示令牌無效。
    7. 完成對所有令牌的處理後,運算元棧中應該只剩下一個元素,即為最終的計算結果。如果運算元棧中的元素不止一個,說明表示式無效,丟擲異常。
  8. IsOperator方法用於檢查一個字串是否為運運算元(+、-、*、/、^)。

  9. GetPrecedence方法用於獲取運運算元的優先順序。

  10. EvaluateOperator方法用於計算兩個運算元和一個運運算元的結果。


 逆波蘭表示式演演算法由荷蘭電腦科學家Edsger Dijkstra在1960年代實現,它也是ALGOL60語言的基礎演演算法,因為在ALGOL60上做出原理性貢獻,Dijkstra獲得了1972年的圖靈獎。而該演演算法的名稱來源於其發明者,波蘭數學家Jan Łukasiewicz由1929年提出此數學方法。

逆波蘭表示式演演算法的數學原理可以透過棧的資料結構來解釋。我們以中綴表示式 "3 + 4 * 2" 為例進行解釋。

  1. 遍歷中綴表示式中的每個令牌(數字、運運算元)。
  2. 如果令牌是數字,則直接將其新增到輸出佇列中。
  3. 如果令牌是運運算元: a. 如果運運算元棧為空,或者運運算元棧頂的運運算元優先順序小於當前運運算元,則將當前運運算元壓入運運算元棧中。 b. 如果運運算元棧頂的運運算元優先順序大於等於當前運運算元,則將運運算元棧頂的運運算元彈出並新增到輸出佇列中,直到滿足條件為止,然後將當前運運算元壓入運運算元棧中。
  4. 處理完所有令牌後,將運運算元棧中剩餘的運運算元依次彈出並新增到輸出佇列中。

以中綴表示式 "3 + 4 * 2" 為例,透過上述步驟,我們可以將中綴表示式轉換為字尾表示式 "3 4 2 * +"。

中綴表示式轉換為字尾表示式的過程是為了更方便地讓計算機進行數學表示式的計算。字尾表示式(也稱為逆波蘭表示式)具有以下優點:

  1. 沒有括號:字尾表示式不需要括號來表示運算的優先順序,因此避免了括號所帶來的歧義和複雜性。

  2. 易於計算:字尾表示式的計算可以透過簡單的迭代演演算法來實現,不需要遞迴或者棧來儲存中間結果,這使得計算更加高效。

對於字尾表示式 "3 4 2 * +",我們可以按照以下步驟進行計算:

  1. 從左到右掃描字尾表示式,遇到運算元就將其壓入棧中。
  2. 遇到運運算元時,從棧中彈出相應數量的運算元進行計算,並將計算結果壓入棧中。
  3. 最終棧中剩下的元素就是整個表示式的計算結果。

對於字尾表示式 "3 4 2 * +",計算過程如下:

  • 遇到 "3",壓入棧中
  • 遇到 "4",壓入棧中
  • 遇到 "2",壓入棧中
  • 遇到 "*",彈出棧頂的兩個元素(4和2),計算結果為8,將結果壓入棧中
  • 遇到 "+",彈出棧頂的兩個元素(3和8),計算結果為11,將結果壓入棧中
  • 最終棧中剩下的唯一元素就是整個表示式的計算結果,即11。

因此,字尾表示式的轉換和計算可以使數學表示式在計算機中的處理更加簡單和高效。

逆波蘭表示式演演算法之所以受到廣泛應用,是因為它能夠有效地解決中綴表示式計算的問題。中綴表示式需要使用括號來確定運運算元的優先順序,而逆波蘭表示式則不需要,因為它使用字尾表示法,運運算元的優先順序可以透過運運算元的順序來確定。

逆波蘭表示式演演算法在計算器、編譯器、直譯器等領域都有廣泛應用。在計算器中,使用者輸入的數學算式,即中綴表示式通常需要轉換為逆波蘭表示式才能進行計算。在編譯器和sql直譯器中,逆波蘭表示式演演算法可以用於計算sql表示式的值,以及將sql表示式轉換為可執行程式碼。


演演算法實現2:

 1 public static double calculate(string s){
 2      s=s.Replace(" ","");
 3      if (Regex.Match(s,"^[(][\\d\\.\\+\\-\\*/\\^]+[)]$").Success) s=Regex.Replace(s,"^[(]|[)]$","");
 4      if (Regex.Match(s,"^-?[\\d\\.]+$").Success) return double.Parse(s);//檢測到單個的正負數字,直接返回值
 5      if (Regex.Match(s,"^\\d[\\d+-\\.]+$").Success) return Regex.Matches(s,"-[\\d\\.]+|(?<=^|[+])[\\d\\.]+").OfType<Match>().Sum(x=>double.Parse(x.Value)); //檢測到只包含連續+-符號的算式,返回其運算值
 6      if (Regex.Match(s,"^[\\d\\.]+[*/\\^][\\d\\.]+$").Success) { //檢測到兩個數的乘除次方,返回其運算值
 7        var tmp=Regex.Split(s,"[*/\\^]").Select(double.Parse).ToArray();
 8        return s.Contains('*') ? tmp[0]*tmp[1] : s.Contains('/') ? tmp[0]/tmp[1] : Math.Pow(tmp[0],tmp[1]);
 9      }
10      if (s.Contains('(')) {  //檢測到括號,先運算一個括號單元,然後遞迴
11        var tmp=Regex.Match(s,"[(][\\d\\.\\+\\-\\*/\\^]+[)]").Value;
12        return calculate(s.Substring(0,s.IndexOf(tmp))+calculate(tmp)+s.Substring(s.IndexOf(tmp)+tmp.Length));
13      }
14      if (s.Contains('^')) {  //檢測次方符號,優先運算
15        var tmp=Regex.Match(s,"[\\d\\.]+[\\^][\\d\\.]+").Value;
16        return calculate(s.Substring(0,s.IndexOf(tmp))+calculate(tmp)+s.Substring(s.IndexOf(tmp)+tmp.Length));
17      }
18      if (s.Contains('*')||s.Contains('/')) {  //檢測乘除符號,優先運算
19        var tmp=Regex.Match(s,"[\\d\\.]+[\\*/][\\d\\.]+").Value;
20        return calculate(s.Substring(0,s.IndexOf(tmp))+calculate(tmp)+s.Substring(s.IndexOf(tmp)+tmp.Length));
21      }
22      return 0;
23    }

演演算法2是一個用於計算數學表示式的演演算法,使用了正規表示式和遞迴的思想,以及一些基本的運運算元處理和字串處理方法。這種型別的演演算法通常用於電腦科學和程式設計中,以便對數學表示式進行求值。

在工作之餘,小悅回想起大學時期的那次考試。當時,她運用逆波蘭表示式演演算法成功解決了算式計算器問題。而現在,憑藉著工作經驗的積累,她採用自己設計的方法——正規表示式和遞迴演演算法。

小悅運用正規表示式的強大功能,將輸入的算式進行精準的解析和拆分。她巧妙地運用正規表示式的分組和捕獲功能,將算式拆分為一個個獨立的子表示式。然後,她利用遞迴演演算法的特性,對這些子表示式進行遞迴計算。這種方法的優點在於,它能夠處理包含括號、運運算元優先順序等複雜元素在內的算式,同時還能保證計算過程的靈活性和高效性。

但演演算法2對於更復雜的指令碼語言直譯器來說,對於更復雜的表示式,比如函式呼叫、變數賦值等,就無法處理,可能需要考慮更多的功能和錯誤處理機制。如果需要實現一個更完整的指令碼直譯器,還是需要透過演演算法1進行擴充套件。


演演算法1和演演算法2的比較如下:

演演算法1優點:

  1. 逆波蘭表示式演演算法不需要使用括號,因此可以減少括號匹配的複雜性。
  2. 逆波蘭表示式演演算法可以直接利用棧結構進行計算,簡化了中綴表示式轉換為字尾表示式的過程。
  3. 逆波蘭表示式演演算法可以用於解釋各種型別的語言和資料格式,從而實現各種直譯器。這些直譯器可以用於實現各種應用,例如命令列工具、頁面指令碼語言、配置檔案解析器、sql資料庫查詢解析器等。

演演算法1缺點:

  1. 中綴表示式轉換為字尾表示式的過程可能較為複雜,需要額外的轉換步驟。
  2. 逆波蘭表示式演演算法需要額外的資料結構(棧)來進行計算,可能需要更多的記憶體空間。

演演算法2優點:

  1. 演演算法2可以直接對中綴表示式進行遞迴計算,不需要額外的轉換步驟。
  2. 演演算法2可以直接處理括號、乘除次方等運運算元,邏輯相對清晰。

演演算法2缺點:

  1. 演演算法2中使用了正規表示式進行匹配,可能在處理複雜表示式時效率較低。
  2. 演演算法2在處理巢狀括號的情況時可能需要多次遞迴計算,效率較低。
  3. 演演算法2對於更復雜的指令碼語言直譯器來說,不容易擴充套件和最佳化。

綜合來看,演演算法1在處理簡單表示式時可能更加高效,而演演算法2在處理複雜數學表示式時可能更加直觀和易於理解。具體選擇哪種演演算法取決於實際需求和對演演算法的偏好。


測試用例:

  1  using NUnit.Framework;
  2   using System;
  3   [TestFixture]
  4   public class CalculatorTest
  5   {
  6     public bool close(double a, double b)
  7     {
  8       if (Math.Abs(a-b)<0.000000001) return true;
  9       return false;
 10     }
 11     [Test]
 12     public void EasyTests()
 13     {
 14       Assert.AreEqual(true, close(Edm.calculate("3 + 5"), 8));
 15       Assert.AreEqual(true, close(Edm.calculate("5 + 41"), 46));
 16       Assert.AreEqual(true, close(Edm.calculate("5 - 3"), 2));
 17       Assert.AreEqual(true, close(Edm.calculate("5 - 5"), 0));
 18       Assert.AreEqual(true, close(Edm.calculate("3 * 5"), 15));
 19       Assert.AreEqual(true, close(Edm.calculate("2 * 23"), 46));
 20       Assert.AreEqual(true, close(Edm.calculate("123 / 3"), 41));
 21       Assert.AreEqual(true, close(Edm.calculate("22 / 1"), 22));
 22     }
 23     
 24     [Test]
 25     public void MediumTests()
 26     {
 27       Assert.AreEqual(true, close(Edm.calculate("3 + 5 * 2"), 13));
 28       Assert.AreEqual(true, close(Edm.calculate("5 - 3 * 8 / 8"), 2));
 29       Assert.AreEqual(true, close(Edm.calculate("6*(2 + 3)"), 30));
 30       Assert.AreEqual(true, close(Edm.calculate("2 ^ 5"), 32));
 31       Assert.AreEqual(true, close(Edm.calculate("5 ^0"), 1));
 32       Assert.AreEqual(true, close(Edm.calculate("23.2- 15.2"), 8));
 33       Assert.AreEqual(true, close(Edm.calculate("22 / 5"), 22.0/5));
 34     }
 35     
 36     [Test]
 37     public void HardTests()
 38     {
 39       Assert.AreEqual(true, close(Edm.calculate("3 * (4 +       (2 / 3) * 6 - 5)"), 9));
 40       Assert.AreEqual(true, close(Edm.calculate("123      -( 4^ (       3 -   1) * 8 - 8      /(     1 + 1 ) *(3 -1) )"), 3));
 41       Assert.AreEqual(true, close(Edm.calculate("4 + 2 * ( (226 - (5 * 3) ^ 2) ^ 2 + (10.7 - 7.4) ^ 2 - 6.89)"),14));
 42       Assert.AreEqual(true, close(Edm.calculate(" (226 - (5 * 3) ^ 2) ^ 2"),1));
 43     }
 44     
 45     [Test]
 46     public void RandomTest() //some really simple random tests just to get this older kata out of beta (@smile67, 26.10.2017)
 47     {
 48        string [] f=new string []{"a * b   -c","a + b/   (b +  c)","(a   + c+  b)  * b  *a"};
 49        Random rnd = new Random();
 50        for (int i=1;i<51;i++) {
 51           int r= rnd.Next(0,3), a= rnd.Next(0,100), b= rnd.Next(0,100), c= rnd.Next(0,100);
 52           string t=f[r].Replace("a",a+"").Replace("b",b+"").Replace("c",c+"");
 53           double s1=Edm.calculate(t), s2=calculate2(t);
 54           Console.WriteLine(i+". Tested for: "+t+", Expected: "+s2+", Got: "+s1);
 55           Assert.AreEqual(true, close(s1, s2));
 56        }
 57     }
 58     
 59     //my old solution (no recursion, so only one function name/call to change - i'm lazy;-))
 60         static int[] priority = new int[11] { 1, 2, 0, -1, -1, 1, 0, 0, 0, 0, 1 };
 61         static double[] numStack = new double[256];
 62         static char[] buffer = new char[256];
 63         
 64         public static double calculate2 (string input)
 65         {
 66             input = input.Replace(" ", "").Replace("^","&")+")";
 67             string number = "";
 68             char ch, chBefore = '(';
 69             int prio, numCount = 0, opCount = 1;
 70             buffer[0] = '('; buffer[1] = '(';
 71             for (int i = 0; i < input.Length; i++)
 72             {
 73                 if (((ch = input[i]) >= '0') || (ch == '.')) number += ch;
 74                 else
 75                 {
 76                     if (chBefore < '0') if (chBefore != ')') if (ch == '-') ch = '%'; 
 77                     if (number.Length > 0) { numStack[++numCount] = Convert.ToDouble(number); number = ""; }
 78                     if (ch > '(')
 79                     {
 80                         prio = priority[ch - 37];
 81                         while (opCount > 0 && prio <= priority[buffer[opCount] - 37])
 82                         {
 83                             --numCount;
 84                             switch (buffer[opCount])
 85                             {
 86                                 case '+': numStack[numCount] = numStack[numCount] + numStack[numCount + 1]; break;
 87                                 case '-': numStack[numCount] = numStack[numCount] - numStack[numCount + 1]; break;
 88                                 case '*': numStack[numCount] = numStack[numCount] * numStack[numCount + 1]; break;
 89                                 case '/': numStack[numCount] = numStack[numCount] / numStack[numCount + 1]; break;
 90                                 case '&': numStack[numCount] = Math.Pow(numStack[numCount], numStack[numCount + 1]); break;
 91                                 case '%': numCount++; numStack[numCount] = -numStack[numCount]; break;
 92                                 case '(': numCount++; prio = 5; break;
 93                             }
 94                             opCount--;
 95                         }
 96                     }
 97                     if (ch != ')') buffer[++opCount] = ch;
 98                 }
 99                 chBefore = ch;
100             }
101             return numStack[1]; 
102         }        
103     
104   }

 

// 返回運算元棧中唯一的元素作為計算結果

相關文章