直接看程式碼,裏面有註釋。
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Scan_demo
{
/*(一)類名:CalculateString
* 功能:計算表達式字串的值。當前支援的運算子包括加(+)減(-)乘(*)除(/)冪(^),支援圓括號(),支援小數。
* 例如:輸入字串" -(369.9+(- (- 55 *6))) + 2.5*3^2",計算後得到數位-677.2
*
*(二)使用的工具類及其功能:
* string: 存放輸入的字串;
* Regex: 檢查語法;
* StringBuilder:對字串進行預處理,如去掉空格、新增分隔符。再利用string類的Split方法把字串分解爲字串陣列。
* Stack:利用其記憶功能,處理計算中的優先順序問題。
* double:把字串轉成double數值
*
*(三)主要演算法:
* 1、生成後綴表達式;
* 2、計算後綴表達式;
*
*/
class CalculateString
{
/// <summary>
/// 計算字串表達式的值。
/// </summary>
/// <param name="exp">輸入的表達式字串</param>
/// <returns>返回計算值,爲double型別</returns>
public static double Compute(string exp)
{
if (!checkRules(exp))
{
throw new FormatException("字串爲空或不合法");
}
//先把字串轉換成後綴表達式字串陣列
string[] rpn = toRPN(toStrings(exp));
//再計算後綴表達式
Stack<double> stack = new Stack<double>(); //存放參與計算的數值、中間值、結果
//演算法:利用foreach來掃描後綴表達式字串陣列,得到數值則直接壓入棧中,
//得到運算子則從棧頂取出兩個數值進行運算,並把結果壓入棧中。最終棧中留下一個數值,爲計算結果。
foreach (string oprator in rpn)
{
//爲什麼總是彈出兩個數值?因爲都是雙目運算。
//先彈出的是運算子右邊的數,彈出兩個數值後注意運算順序。
switch (oprator)
{
case "+":
//如果讀取到運算子,則從stack中取出兩個數值進行運算,並把運算結果壓入stack。下同。
stack.Push(stack.Pop() + stack.Pop());
break;
case "-":
stack.Push(-stack.Pop() + stack.Pop());
break;
case "*":
stack.Push(stack.Pop() * stack.Pop());
break;
case "/":
{
double right = stack.Pop();
try
{
stack.Push(stack.Pop() / right);
}
catch (Exception e)
{
throw e; //除數爲0時產生異常。
}
break;
}
case "^":
{
double right = stack.Pop();
stack.Push(Math.Pow(stack.Pop(),right));
break;
}
default: //後綴表達式陣列中只有運算子和數值,沒有圓括號。除了運算子,剩下的就是數值了
stack.Push(double.Parse(oprator)); //如果讀取到數值,則壓入stack中
break;
}
}
//彈出最後的計算值並返回
return stack.Pop();
}
/// <summary>
/// 檢查字串,判斷是否滿足表達式的語法要求。
/// </summary>
/// <param name="exp">表達式字串</param>
/// <returns>字串爲空或不滿足表達式語法要求時返回false,否則返回true</returns>
public static bool checkRules(string exp)
{
if (string.IsNullOrWhiteSpace(exp))
{
return false;
}
//去掉空格
string noBlank = Regex.Replace(exp, " ", "");
//Console.WriteLine(noBlank);
//表達式字串規則。規則之間有配合關係,以避免重疊。
string no0 = @"[^\d\*\^\(\)+-/.]"; //規則0:不能出現運算子+-*/^、圓括號()、數位、小數點.之外的字元
string no1 = @"^[^\d\(-]"; //規則1:開頭不能是數位、左圓括號(、負號- 以外的字元
string no2 = @"[^\d\)]$"; //規則2:結束不能是數位、右圓括號) 以外的字元
string no3 = @"[\*\^+-/]{2}"; //規則3:+-*/^不能連續出現
string no4 = @"[\D][.]|[.]\D"; //規則4:小數點前面或後面不能出現非數位字元
string no5 = @"\)[\d\(]|[^\d\)]\)"; //規則5:右圓括號)後面不能出現數字、左圓括號(,前面不能出現除數位或右圓括號)以外的字元
string no6 = @"\([^\d\(-]|[\d]\("; //規則6:左圓括號(後面不能出現除數位、左圓括號(、負號以外的字元,前面不能出現數字
string pattern = no0 + "|" + no1 + "|" + no2 + "|" + no3 + "|" + no4 + "|" + no5 + "|" + no6;
if (Regex.IsMatch(noBlank, pattern))
{
return false;
}
//規則7:左圓括號(和右圓括號)必須成對出現
int count = 0;
foreach (char c in noBlank)
{
if (c == ')')
{
count++;
continue;
}
if (c == '(')
{
count--;
continue;
}
}
if (count != 0)
{
Console.WriteLine("左右括號不匹配");
return false;
}
return true;
}
/// <summary>
/// 將表達式字串轉換成字串陣列,如"-2*57+6"轉換成["-2","*","57","+","6"]。請自行確保表達式字串正確。
/// </summary>
/// <param name="exp">表達式字串</param>
/// <returns>字串陣列</returns>
public static string[] toStrings(string exp)
{
//通過新增分割符','把數位和其它字元分隔開。
StringBuilder sb = new StringBuilder(exp);
//去掉空格
sb.Replace(" ","");
//負號與減號相同,不能與數位分開,需要特許處理:
//1、字串第一個"-"是負號
//2、緊跟在"("後面的"-"是負號
//3、如果負號後面直接跟着"(",把負號改爲"-1*"。目的是把取負運算(單目運算)變成乘法運算(雙目運算),免得以後要區分減法和取負。
//4、把負號統一改爲"?"
if (sb[0] == '-' )
{
sb[0] = '?';
}
sb.Replace("(-", "(?");
sb.Replace("?(", "?1*(");
//新增分割符','把數位和其它字元分隔開。
sb.Replace("+", ",+,");
sb.Replace("-", ",-,");
sb.Replace("*", ",*,");
sb.Replace("/", ",/,");
sb.Replace("(", "(,");
sb.Replace(")", ",)");
sb.Replace("^", ",^,");
//分割之後,把'?'恢復成減號 '-'
sb.Replace('?', '-');
return sb.ToString().Split(',');
}
/// <summary>
/// 生成後波蘭表達式(後綴表達式)
/// </summary>
/// <param name="expStrings">字串陣列(中綴表達式)</param>
/// <returns>字串陣列(後綴表達式)</returns>
public static string[] toRPN(string[] expStrings)
{
Stack<string> stack = new Stack<string>();
List<string> rpn = new List<string>();
//基本思路:
//遍歷expStrings中的字串:
//1、如果不是字元(即數位)就直接放到列表rpn中;
//2、如果是字元:
//2.1、如果stack爲空,把字元壓入stack中;
//2.2、如果stack不爲空,把棧中優先順序大於等於該字元的運算子全部彈出(直到碰到'('或stack爲空),放到rpn中;
//2.2 如果字元是'(',直接壓入
//2.3 如果是')',依次彈出stack中的字串放入rpn中,直到碰到'(',彈出並拋棄'(';
foreach (string item in expStrings)
{
//1、處理"("
if (item == "(")
{
stack.Push(item);
continue;
}
//2、處理運算子 +-*/^
if ("+-*/^".Contains(item))
{
if (stack.Count == 0 )
{
stack.Push(item);
continue;
}
if (getOrder(item[0]) > getOrder(stack.Peek()[0]))
{
stack.Push(item);
continue;
}
else
{
while (stack.Count > 0 && getOrder(stack.Peek()[0])>= getOrder(item[0]) && stack.Peek() != "(")
{
rpn.Add(stack.Pop());
}
stack.Push(item);
continue;
}
}
//3、處理")"
if (item == ")")
{
while (stack.Peek() != "(")
{
rpn.Add(stack.Pop());
}
stack.Pop();//拋棄"("
continue;
}
//4、數據,直接放入rpn中
rpn.Add(item);
}
//最後把stack中的運算子全部輸出到rpn
while (stack.Count > 0)
{
rpn.Add(stack.Pop());
}
//把字串鏈表轉換成字串陣列,並輸出。
return rpn.ToArray();
}
/// <summary>
/// 獲取運算子的優先順序
/// </summary>
/// <param name="oprator">運算子</param>
/// <returns>運算子的優先順序。</returns>
private static int getOrder(char oprator)
{
switch (oprator)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 3;
case '^':
return 5;
//case '(':
// return 10;
default:
return -1;
}
}
}
}