不久之前我写过一篇与或逻辑运算的实现及格式验证的文章,其中验证部分我使用了正则表达式,但计算部分还是依靠基本流程处理的。后来想了想,计算是否也能使用正则表达式呢?再做一个逻辑表达式计算就没太大意思了,这次咱来试试四则运算。
我的基本思路是先乘除后加减,先运算式子中简单的乘除法例如:“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); } }