C# 计算一串字符串算法
工作中遇到一个小问题,就是要做一个类似excel那种的公式的东西,就是A0+A1*B0那样的公式,然后得出结果。
首先,分析。
这不是计算器,计算器要比这个简易,计算器是所按即所得,即你点击+-之类的按钮时候,你的数字已经确认了,你所要做的只是转换一下string和decimal而已。
比如1+2*(2+4)/4-1
如果再算上幂运算,我打不出来幂运算的符号,尴尬。
我们可以这么写,比如,遇到的第一个数字是1,那么定义一个变量firnum=1 第一个符号是+,定义一个变量 mark=+,第二个数字是2,顶一个一个变量secnum=2,第二个符号是*,这时候进行判断,因为*/比加减的运算级别高,要先算乘除,所以,这里1和+要缓存起来,继续往下走,然后计算(),得出()内的数字是6,这时候先运算2*6,然后遇到/,计算12/4,再往后走,遇到-,这时候+-的运算级别一样,则开始运算之前的1和+,然后依次运算,最后得出结果。
怎么说呢,我们姑且认为这是一个方法吧,姑且认为,这么辛苦了,写了这么多代码,能进行四则运算,还挺正确,也不容易,没有功劳也有苦劳。
public decimal CalcRet(string str) { //第一个数字 string firStr = string.Empty; decimal firNum = 0m; //第二个数字; string secStr = string.Empty; decimal secNum = 0m; //temp数字 string tempStr = string.Empty; //当前计算符号 char curMark = '!'; //结果 decimal result = 0m; //上一个符号 char lastMark = '!'; for (int i = 0; i < str.Length; i++) { char c = str[i]; //判断如果是数字和. if ((47 < c && c < 58) || c == '.') { //除却第一次是第一个数字需要转换,以后都是第一个和第二个进行累加 if (curMark == '!') { firStr += c; } else { if (curMark == '+' || curMark == '-') { secStr += c; } else if (curMark == '*' || curMark == '/') { if (lastMark == '+' || lastMark == '-') { tempStr += c; } else { secStr += c; } } } continue; } if (firStr != "") { decimal.TryParse(firStr, out firNum); firStr = ""; } if (c == '+' || c == '-' || c == '*' || c == '/') { switch (curMark) { case '+': if (secStr != "" && tempStr != "") { secNum = OperCalc(curMark, secNum, tempStr); firNum = firNum + secNum; secStr = ""; tempStr = ""; } if (c == '*' || c == '/') { lastMark = curMark; curMark = c; break; } if (secStr == "") continue; firNum = OperCalc(curMark, firNum, secStr); curMark = c; lastMark = c; secStr = ""; break; case '-': if (secStr != "" && tempStr != "") { secNum = OperCalc(curMark, secNum, tempStr); firNum = firNum - secNum; secStr = ""; tempStr = ""; } if (c == '*' || c == '/') { lastMark = curMark; curMark = c; break; } if (secStr == "") continue; firNum = OperCalc(curMark, firNum, secStr); curMark = c; lastMark = c; secStr = ""; break; case '*': if (lastMark != '!' && tempStr != "") { secNum = OperCalc(curMark, secStr, tempStr); secStr = secNum.ToString(); tempStr = ""; } else { firNum = OperCalc(curMark, firNum, secStr); secStr = ""; curMark = c; break; } if (c == '+' || c == '-') { if (lastMark != '!') { firNum = OperCalc(lastMark, firNum, secNum); secStr = ""; tempStr = ""; } } curMark = c; break; case '/': if (lastMark != '!' && tempStr != "") { secNum = OperCalc(curMark, secStr, tempStr); secStr = secNum.ToString(); tempStr = ""; } else { firNum = OperCalc(curMark, firNum, secStr); secStr = ""; curMark = c; break; } if (c == '+' || c == '-') { if (lastMark != '!') { firNum = OperCalc(lastMark, firNum, secNum); secStr = ""; tempStr = ""; } } curMark = c; break; case '(': break; case ')': break; default: curMark = c; if (c == '+' || c == '-') lastMark = c; break; } } else if (c == '(') { int temp = 1; for (int j = i + 1; j < str.Length; j++) { var k = str[j]; if (k == '(') { temp++; } else if (k == ')') { temp--; } if (temp == 0) { temp = j - i - 1; } } var kh = CalcRet(str.Substring(i + 1, temp)); if (lastMark != '!') { if (secStr != "") { tempStr = kh.ToString(); } else { secNum = kh; secStr = kh.ToString(); } } else { if (i == 0) { firNum = kh; } else { secNum = kh; secStr = kh.ToString(); } } i += temp + 1; } } if (tempStr != "") { secNum = OperCalc(curMark, secStr, tempStr); secStr = secNum.ToString(); result = OperCalc(lastMark, firNum, secStr); } else { result = OperCalc(curMark, firNum, secStr); } return result; } decimal OperCalc(char mark, string fir, string sec) { decimal a, b; decimal.TryParse(fir, out a); decimal.TryParse(sec, out b); switch (mark) { case '+': return a + b; case '-': return a - b; case '*': return a * b; case '/': return a / b; default: return 0m; } } decimal OperCalc(char mark, decimal fir, string sec) { decimal b; decimal.TryParse(sec, out b); switch (mark) { case '+': return fir + b; case '-': return fir - b; case '*': return fir * b; case '/': return fir / b; default: return 0m; } } decimal OperCalc(char mark, decimal fir, decimal sec) { switch (mark) { case '+': return fir + sec; case '-': return fir - sec; case '*': return fir * sec; case '/': return fir / sec; default: return 0m; } }
对,就是这种写法。
在我看来,这么写的代码,真的只是一堆垃圾,我不是针对谁,我是说写成这样的逻辑,它就是垃圾,连没毕业的大学生都不如。
比如,如果加幂运算如果加mod怎么办,我就问你怎么办?
继续判断?
写不死你!
然后,我们可以换个思路。
所谓运算,不过是两个数字和一个符号之间故事,抱歉,我觉得一对一那种男女关系不适用于这里,开个玩笑,呵呵!强行尬聊~
1+2 是1,2 两个数字和+之间的运算
1+2+3*(4+5),是12345数字和四个符号进行的运算,至于括号,我们是不是可以把括号当成一个递归?就是4+5当成一个递归,调用同一个函数,返回一个结果就行了
也就是说,数字永远比符号多一个
我们是不是可以这么想。
list1 ={1,2,3,4,5}
list2={+,+,*,(+)}
第一次运算后
list1 ={1,2,3,9}
list2={+,+,*}
按照优先级,我们可先计算*
得到
list1 ={1,2,3,9}{1,2,27}
list2={+,+,*}{+,+}
删掉*和最后的9,同时删掉一个符号和一个数字,得到
list1 ={1,2,27}
list2={+,+}
继续
list1 ={3,27}
list2={+}
再继续
list1 ={30}
list2={}
最后就剩下一个数字,好,计算完毕
public class CalcOperation { /// <summary> /// 计算字符串解析表达式 1+2(2*(3+4)) /// </summary> /// <param name="str">传入的字符串</param> /// <returns>计算得到的结果</returns> public decimal CalcStr(string str) { decimal num = 0m; //数字集合 List<decimal> numList = new List<decimal>(); //操作符集合 List<Operation> operList = new List<Operation>(); string strNum = ""; for (int i = 0; i < str.Length; i++) { char c = str[i]; //判断如果是数字和. if ((47 < c && c < 58) || c == '.') { strNum += c; if (i == str.Length - 1) { if (!string.IsNullOrEmpty(strNum)) { decimal.TryParse(strNum, out num); numList.Add(num); strNum = ""; } } continue; } else if (c == '(') { int temp = 1; for (int j = i + 1; j < str.Length; j++) { var k = str[j]; if (k == '(') { temp++; } else if (k == ')') { temp--; } if (temp == 0) { temp = j - i - 1; } } strNum = str.Substring(i + 1, temp); numList.Add(CalcStr(strNum)); strNum = ""; i += temp + 1; } else { if (!string.IsNullOrEmpty(strNum)) { decimal.TryParse(strNum, out num); numList.Add(num); strNum = ""; } if (c == '+') { operList.Add(new AddOperation()); } else if (c == '-') { operList.Add(new SubOperation()); } else if (c == '*') { operList.Add(new MultipOperation()); } else if (c == '/') { operList.Add(new DivOperation()); } else if (c == '%') { operList.Add(new ModOperation()); } else { operList.Add(null); } } } List<int> tempOrder = new List<int>(); operList.ForEach(w => { if (!tempOrder.Contains(w.PrioRity)) { tempOrder.Add(w.PrioRity); } }); tempOrder.Sort(); for (int t = 0; t < tempOrder.Count; t++) { for (int i = 0; i < operList.Count; i++) { if (operList[i].PrioRity == tempOrder[t]) { numList[i] = operList[i].OperationResult(numList[i], numList[i + 1]); numList.RemoveAt(i + 1); operList.RemoveAt(i); i--; } } } if (numList.Count == 1) return numList[0]; return 0m; } public class Operation { protected int priority = 99; /// <summary> /// 优先级 /// </summary> public virtual int PrioRity { get { return priority; } set { priority = value; } } public virtual decimal OperationResult(decimal a, decimal b) { return 0m; } } public class AddOperation : Operation { public override decimal OperationResult(decimal a, decimal b) { return a + b; } } public class SubOperation : Operation { public override decimal OperationResult(decimal a, decimal b) { return a - b; } } public class MultipOperation : Operation { public override int PrioRity { get { return 98; } } public override decimal OperationResult(decimal a, decimal b) { return a * b; } } public class DivOperation : Operation { public override int PrioRity { get { return 98; } } public override decimal OperationResult(decimal a, decimal b) { return a / b; } } public class ModOperation : Operation { public override int PrioRity { get { return 97; } } public override decimal OperationResult(decimal a, decimal b) { return a % b; } } }
PrioRity这个是优先级,我比较懒,就从99往上了
但是这样真的很明了啊,而且可以随时添加新的算法,简直了
我想说的是,能简便的尽量简便,能通运的尽量通用,自己看的舒服,别人看的也舒服,是不是~