不久之前我寫過一篇與或邏輯運算的實現及格式驗證的文章,其中驗證部分我使用了正則表達式,但計算部分還是依靠基本流程處理的。后來想了想,計算是否也能使用正則表達式呢?再做一個邏輯表達式計算就沒太大意思了,這次咱來試試四則運算。
我的基本思路是先乘除后加減,先運算式子中簡單的乘除法例如:“2*5”,但“2*(3+4)”不必處理先,咱們先解決簡單的。然后處理簡單的加減法,例如"3+4",同樣涉及括號的先不處理。最后去括號,但是只去無用的括號,例如“(3)”,這樣一來連續的式子(例如:2*8+9/3)總是能化成“(19)”,這樣最終被去掉括號變成“19”再與其他部分做運算。只需要重復以上的部分最終就能完成運算了。但其實其中還漏了一步,那就是做完乘除運算之后還有一些應該在加減之前先運算的沒有被保護起來,例如:“(1/2+2*3+1)/2+2*3”做完乘除運算之后式子為“(0.5+6+1)/2+6”如果現在執行加減運算,由於“2+6”能夠被匹配,所以會被先計算那樣就錯了。所以先要把“(0.5+6+1)/2”作為一個整體保護起來,我第一想到的辦法就是加括號即“((0.5+6+1)/2)+6”,這樣就不會被匹配了。但是又有一個問題:如何確定哪些位置需要加括號呢?首先要加括號的地方一定是相乘除的兩個部分中剛好有一個是帶括號的式子另一個卻是普通的數字,不然它一定會在上一步中被消滅掉,或者兩個部分都帶有括號意味着他們不需要先計算,不需要理會,其次他們沒有被括號包圍。如果用正則表達式來匹配似乎比較困難,原因是有括號的那一部分中可能有不確定的內層括號,不能簡單的用“\(\S+\)”,否則就會出大亂子了。那怎么辦呢?想了很久,發現這並不是一種好的解決方法,我開始換一種思路,既然添加括號不方便,那么能不能不加括號卻不會造成錯誤呢?答案是有!例如在“(0.5+6+1)/2+6”中,先計算括號內的簡單加減式而不管外面的加減式,也就是在加減運算的正則表達式中首尾加上括號即可。這樣的話最終會照成算式無法完成,原因是沒有括號的加減法始終不能被匹配。要解決這個問題我們還需要加多一個正在表達式來做最后的工作,這個表達式必須能匹配簡單加減式,即“((\d+(\.(\d+))?)((\+|\-)(\d+(\.(\d+))?))*)”,且算式匹配的值剛好等於算式本身,否則繼續循環下去。這樣真的沒有問題嗎?下面我把源代碼貼出來,供大家參考:
string Calculate(Match match) { string result = match.Value; result = result.Replace("(", ""); result = result.Replace(")", ""); char Operator = '+'; double X = 0; double Y = 0; int index = 0; for (int i = 0; i < result.Length; i++) { if (result[i] == '+' || result[i] == '-' || result[i] == '*' || result[i] == '/' || i == result.Length - 1) { if (i == result.Length - 1) { Y = Convert.ToDouble(result.Substring(index, i - index + 1)); } else { Y = Convert.ToDouble(result.Substring(index, i - index)); } index = i + 1; switch (Operator) { case '+': X = X + Y; break; case '-': X = X - Y; break; case '*': X = X * Y; break; case '/': X = X / Y; break; } Operator = result[i]; } } return X.ToString(); }string Eliminate(Match match) { return match.Value.Substring(1, match.Value.Length - 2); } internal string Calculation(string expression) { Regex regexMultiplication = new Regex(@"((\d+(\.(\d+))?)((\*|\/)(\d+(\.(\d+))?))*)"); Regex regexAddition = new Regex(@"\(((\d+(\.(\d+))?)((\+|\-)(\d+(\.(\d+))?))*)\)"); Regex regexEliminate = new Regex(@"\(\d+(\.(\d+))?\)"); Regex regexComplete = new Regex(@"((\d+(\.(\d+))?)((\+|\-)(\d+(\.(\d+))?))*)"); while (true) { expression = regexMultiplication.Replace(expression, Calculate); expression = regexAddition.Replace(expression, Calculate); expression = regexEliminate.Replace(expression, Eliminate); if (regexComplete.Match(expression).Value == expression) { return expression = regexComplete.Replace(expression, Calculate); } } }
事實上以上代碼中仍然存在不能支持包含負數的運算的問題,例如“5+(2-9)”,“(2-9)”會被先計算成"(-7)",它不能被任何一個正則表達式匹配。為了解決這個問題,我修改了源代碼,在原來匹配數字的地方的前面加上"[-+]?",這樣數字前就能夠包含正負號了。我也完全修改了簡單計算的算法並將加減運算和乘除運算分開(原因是包含正負號之后處理邏輯不同),具體如下:
class Arithmetic { Regex regexMultiplicationAndDivision = new Regex(@"(([-+]?\d+(\.(\d+))?)((\*|\/)([-+]?\d+(\.(\d+))?))+)"); Regex regexAdditionAndSubtraction = new Regex(@"\((([-+]?\d+(\.(\d+))?)((\+|\-)([-+]?\d+(\.(\d+))?))+)\)"); Regex regexEliminate = new Regex(@"\([-+]?\d+(\.(\d+))?\)"); Regex regexComplete = new Regex(@"(([-+]?\d+(\.(\d+))?)((\+|\-)([-+]?\d+(\.(\d+))?))*)"); Regex regexError = new Regex(@"\)\(|\)(\d+(\.(\d+))?)|(\d+(\.(\d+))?)\("); internal string Calculation(string expression) { if (regexError.IsMatch(expression)) { throw new Exception(); } while (true) { int iNotMatch = 0; if (regexMultiplicationAndDivision.IsMatch(expression)) { expression = regexMultiplicationAndDivision.Replace(expression, MultiplicationAndDivision); } else { iNotMatch++; } if (regexAdditionAndSubtraction.IsMatch(expression)) { expression = regexAdditionAndSubtraction.Replace(expression, AdditionAndSubtraction); } else { iNotMatch++; } if (regexEliminate.IsMatch(expression)) { expression = regexEliminate.Replace(expression, Eliminate); } else { iNotMatch++; } if (regexComplete.Match(expression).Value == expression) { return Convert.ToDouble(regexComplete.Replace(expression, AdditionAndSubtraction)).ToString(); } if (iNotMatch == 3) { throw new Exception(); } } } string MultiplicationAndDivision(Match match) { string text = match.Value; bool isPositive = true; foreach (char c in text) { if (c == '-') { isPositive = !isPositive; } } text = text.Replace("*+", "*"); text = text.Replace("*-", "*"); text = text.Replace("/+", "/"); text = text.Replace("/-", "/"); text = text.Replace("*", ",*"); text = text.Replace("/", ",/"); string[] numbers = text.Split(','); double result = Convert.ToDouble(numbers[0]) >= 0 ? Convert.ToDouble(numbers[0]) : (-Convert.ToDouble(numbers[0])); for (int i = 1; i < numbers.Length;i++ ) { if (numbers[i] != "") { switch (numbers[i][0]) { case '*': result *= Convert.ToDouble(numbers[i].Substring(1, numbers[i].Length - 1)); break; case '/': result /= Convert.ToDouble(numbers[i].Substring(1, numbers[i].Length - 1)); break; } } } if (isPositive == false) { result = -result; } return result >= 0 ? ("+" + result.ToString()) : result.ToString(); } string AdditionAndSubtraction(Match match) { string text = match.Value; text = text.Replace("(", ""); text = text.Replace(")", ""); text = text.Replace("++", "+"); text = text.Replace("+-", "-"); text = text.Replace("-+", "-"); text = text.Replace("--", "+"); text = text.Replace("+", ",+"); text = text.Replace("-", ",-"); string[] numbers = text.Split(','); double result = 0; foreach (string number in numbers) { if (number != "") { result += Convert.ToDouble(number); } } return result >= 0 ? ("+" + result.ToString()) : result.ToString(); } string Eliminate(Match match) { return match.Value.Substring(1, match.Value.Length - 2); } }