一、基本想法
1、整数和分数的四则运算
由于四则运算要支持分数,刚开始我想着是自定义分数这种数据类型,而后再重载运算符。可当整数和分数混合运算的话,就要考虑到数据类型间的转化,比较麻烦。于是我转化了下思路,即将整数看成是特殊的分数(分母为1),这样将整数和分数统一起来,那么初始化便可以根据具体的数据类型做出调整,如下图所示:
这里需要特别注意的是,我们最后控制台显示的算式是String类型,因而要能实现分数Fraction和String直接的相互转化。
而且要自定义一种隐式转化,这样就能令分数表示出正确的形式(比如整数10不显示分母,分数2/5等)
这时问题又出来了,分数2/4等同于1/2,即在实际应用中分数要进行相应化简,解决方案是求取分母分子的最大公约数,并且如果分母为负数,则将分子分母同时乘以-1(为了比较大小乘以分子大小符号不会改变),如下图所示:
最后便是运算符的重载了,这里以+、<、==为例
其中Add函数具体实现如下:
由于减法等同于加一个负数,除法实际上是乘一个倒数,这里就不再赘述
另外分子不能为0,因此要进行相应的异常处理。
2、运算符的扩展
实际应用中四则运算不单单局限于二元运算,而是可以有多个操作符的混合运算,显然正常的算式顺序是很难计算的,这时候就应该把算式转化成逆波兰式。具体操作步骤如下:
(1)首先把普通的表达式按照运算符分离出来放在一个集合S中,比如3+2*5 分离后集合里的元素就是 3 + 2 * 5 五个元素
(2)再定义一个集合T(为了省去转化类型的麻烦,一般为String),主要用来存放逆波兰表达式的,除此之外需定义一个堆栈K以便存储运算符,最后从左到右遍历集合S
(3)遍历E的规则如下:
(3.1)如果该元素是数字(这里是Fraction转化的String),直接把它添加到集合T中
(3.2)否则它肯定是运算符,那么再进行判断
(3.2.1)如果该元素是左括号,或者当时栈为空,那么直接入栈
(3.2.2)如果该元素是右括号,则把栈内的运算符出栈并添加到集合T中,直到遇到第一个左括号结束(左括号也出栈但不添加到T)
(3.2.3)否则该元素是普通的运算符(也就是+-*/之类的),那么用该运算符和栈内的运算符号比较优先级,如果该运算符的优先级比栈内的运算符优先级高或者栈为空,则直接入栈,否则把栈内的运算符出栈并添加到T中,再判断下个栈内的运算符优先级,直到栈内的运算符优先级<=该运算符或者栈为空时再 把该运算符入栈
这里运算符优先级的定义使用的是Dictionary的数据结构,如图所示:
具体函数实现为 static Queue<object> PreOrderToPostOrder(List<string> expression),最后返回的是集合T
计算逆波兰式的规则相应比较简单,即
(1)从左到右遍历T
(2)如果该元素是数字,直接入栈
(3)如果该元素是运算符,出栈两个数,计算结果再入栈,逆波兰遍历完后栈内的元素就是表达式的值了。函数实现如下:
3、程序流程
目前程序设计流程为:
(1)询问用户生成四则运算的题数
(2)询问用户是否自己输入答案,若输入,完成后统计用户答题的正确数
(3)询问用户是否显示正确答案
(4)询问用户是否继续生成四则运算的题数,若否则询问是否生成题目文件,若要生成题目文件,则再询问生成题目的总题数
从流程中可以看到程序的重中之重是要生成算式,起初我的想法是随机生成运算符(括号除外)的个数,那么表达式其实就是操作数+运算符+操作数的形式,那么相应操作数的个数其实就比运算符的个数多1,可一旦考虑到括号,数组的结构无法满足在原先算式的基础上添加括号,因而我使用的是List这一数据结构,方便插入修改。另外为了表示是操作数还是运算符,除去定义str这一List,还定义了isOperand列表(isOperand的元素为0表示为运算符,为正整数表示第几个操作数,如等于1便是第一个操作数)
这样的话随机了括号的个数numOfBrackets,并随机括号的初始和结束位置(表示在第几个操作数前或后,即start和end),而后再将(、)插入到List str里,相应的isOperand插入-1,表示括号
最后我们在List str首部插入通过已插入元素组合成的算式兵返回。举例2*(5+2)来说,那么str[0] = 2*(5+2),str[1]=2,str[2]=*,str[3]=5,str[4]=+,
str[5]=2,str[6]=)。(这里space表示“ ”,主要是为了算式美观,将分数的/和除法的/区分开,另外考虑到以后可能要加负数,防止混淆)
二、代码实现
代码使用C#语言实现,主要包含Program.cs和Fraction.cs两个文件,前者是主程序,后者是分数类的具体实现。
(1)Program.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.IO; 7 using Fra; 8 9 10 namespace CalC 11 { 12 class Program 13 { 14 static Random ran = new Random(); 15 static Dictionary<string, int> priorities = null; 16 const string operators = "+-*/="; 17 const string space = " "; 18 static Program() 19 { 20 priorities = new Dictionary<string, int>(); 21 priorities.Add("#", -1); 22 priorities.Add("+", 0); 23 priorities.Add("-", 0); 24 priorities.Add("*", 1); 25 priorities.Add("/", 1); 26 } 27 28 static void Main(string[] args) 29 { 30 int input = 1; 31 while (input == 1) 32 { 33 Console.Write("请输入生成四则运算题的数目: "); 34 int numOfQue = int.Parse(Console.ReadLine()); 35 string[] inputAnswer = new string[numOfQue]; 36 string[] correctAnswer = new string[numOfQue]; 37 string[] ques = new string[numOfQue]; 38 List<string> result = new List<string>(); 39 for (int i = 0; i < numOfQue; i++) 40 { 41 result = produceQue(); 42 ques[i] = result[0]; 43 Console.Write("{0,-20}", ques[i] + operators[4]); 44 correctAnswer[i] = Calucate(result); 45 } 46 Console.WriteLine(); 47 Console.Write("是否输入答案(输入1表示用户输入答案,否则不输入): "); 48 input = int.Parse(Console.ReadLine()); 49 if (input == 1) 50 { 51 for (int i = 0; i < numOfQue; i++) 52 { 53 Console.Write("{0,-20}", ques[i] + operators[4] + space); 54 inputAnswer[i] = Console.ReadLine(); 55 } 56 57 int numOfCorrect = 0; 58 for (int i = 0; i < numOfQue; i++) 59 { 60 if (inputAnswer[i] == correctAnswer[i]) 61 numOfCorrect++; 62 } 63 Console.WriteLine("您共答对" + numOfCorrect + "道题"); 64 } 65 66 67 Console.Write("是否显示正确答案(输入1表示显示正确答案,否则不显示): "); 68 input = int.Parse(Console.ReadLine()); 69 if (input == 1) 70 { 71 for (int i = 0; i < numOfQue; i++) 72 Console.Write("{0,-20}", ques[i] + operators[4] + space + correctAnswer[i]); 73 Console.WriteLine(); 74 } 75 Console.Write("是否继续生成四则运算题的数目(输入1继续生成,否则不生成): "); 76 input = int.Parse(Console.ReadLine()); 77 Console.Clear(); 78 } 79 80 Console.Write("是否生成题目文件(输入1生成,否则不生成): "); 81 input = int.Parse(Console.ReadLine()); 82 if (input == 1) 83 { 84 Console.Write("输入生成题目的数量: "); 85 string filename = "que.txt";//这里是你的已知文件 86 FileStream fs = File.Create(filename); //创建文件 87 fs.Close(); 88 StreamWriter sw = new StreamWriter(filename); 89 input = int.Parse(Console.ReadLine()); 90 for (int i = 0; i < input; i++) 91 { 92 string que = ""; 93 que = produceQue()[0]; 94 sw.Write("{0,-20}",que + operators[4] + space); 95 if (i % 10 == 9) 96 sw.Write("\r\n"); 97 } 98 sw.Close(); 99 } 100 } 101 102 103 static List<string> produceQue() 104 { 105 List<string> str = new List<string>(); 106 List<int> isOperand = new List<int>(); 107 int count = 0; 108 count = ran.Next(1, 3); 109 int[] num = new int[count + 1]; 110 int[] den = new int[count + 1]; 111 string[] operand = new string[count + 1]; 112 int[] index = new int[count]; 113 int numOfBrackets = 0; 114 for (int i = 0; i < count + 1; i++) 115 { 116 num[i] = ran.Next(2, 5); 117 if (ran.Next(1, 10) < 8) 118 den[i] = 1; 119 else 120 { 121 den[i] = ran.Next(1, 5); 122 numOfBrackets = ran.Next(1, count); 123 } 124 operand[i] = new Fraction(num[i], den[i]).ToString(); 125 if (i < count) 126 index[i] = ran.Next(0, 4); 127 } 128 int[] start = new int[numOfBrackets]; 129 int[] end = new int[numOfBrackets]; 130 for (int i = 0; i < numOfBrackets; i++) 131 { 132 start[i] = ran.Next(1, count + 1); 133 end[i] = ran.Next(start[i] + 1, count + 2); 134 } 135 int j = 1; 136 for (int i = 0; i < count + 1; i++) 137 { 138 str.Add(operand[i]); 139 isOperand.Add(i + 1); 140 if (i < count) 141 { 142 str.Add(operators[index[i]].ToString()); 143 isOperand.Add(0); 144 } 145 } 146 for (int i = 0; i < numOfBrackets; i++) 147 { 148 int left = isOperand.FindIndex(s=>s==start[i]); 149 str.Insert(left, "("); 150 isOperand.Insert(left, -1); 151 int right = isOperand.FindIndex(s =>s==end[i]); 152 str.Insert(right + 1, ")"); 153 isOperand.Insert(right + 1, -1); 154 } 155 str.Insert(0, ""); 156 for (int i = 1; i < str.Count;) 157 { 158 str[0] += str[i++] + space; 159 } 160 return str; 161 } 162 163 164 165 166 static string Compute(Fraction leftNum, Fraction rightNum, int op) 167 { 168 switch (op) 169 { 170 case 0: return leftNum + rightNum; 171 case 1: return leftNum - rightNum; 172 case 2: return leftNum * rightNum; 173 case 3: return leftNum / rightNum; 174 default: return ""; 175 } 176 } 177 178 static bool IsOperator(string op) 179 { 180 181 return operators.IndexOf(op) >= 0; 182 } 183 184 static bool IsLeftAssoc(string op) 185 { 186 return op == "+" || op == "-" || op == "*" || op == "/" || op == "%"; 187 } 188 189 static Queue<object> PreOrderToPostOrder(List<string> expression) 190 { 191 var result = new Queue<object>(); 192 var operatorStack = new Stack<string>(); 193 operatorStack.Push("#"); 194 string top, cur, tempChar; 195 string tempNum; 196 197 for (int i = 1; i < expression.Count; ) 198 { 199 cur = expression[i++]; 200 top = operatorStack.Peek(); 201 202 if (cur == "(") 203 { 204 operatorStack.Push(cur); 205 } 206 else 207 { 208 if (IsOperator(cur)) 209 { 210 while (IsOperator(top) && ((IsLeftAssoc(cur) && priorities[cur] <= priorities[top])) || (!IsLeftAssoc(cur) && priorities[cur] < priorities[top])) 211 { 212 result.Enqueue(operatorStack.Pop()); 213 top = operatorStack.Peek(); 214 } 215 operatorStack.Push(cur); 216 } 217 else if (cur == ")") 218 { 219 while (operatorStack.Count > 0 && (tempChar = operatorStack.Pop()) != "(") 220 { 221 result.Enqueue(tempChar); 222 } 223 } 224 else 225 { 226 tempNum = cur; 227 result.Enqueue(tempNum); 228 } 229 } 230 } 231 while (operatorStack.Count > 0) 232 { 233 cur = operatorStack.Pop(); 234 if (cur == "#") continue; 235 if (operatorStack.Count > 0) 236 { 237 top = operatorStack.Peek(); 238 } 239 240 result.Enqueue(cur); 241 } 242 243 return result; 244 } 245 246 static string Calucate(List<string> expression) 247 { 248 249 var rpn = PreOrderToPostOrder(expression); 250 var operandStack = new Stack<string>(); 251 string left, right; 252 object cur; 253 while (rpn.Count > 0) 254 { 255 cur = rpn.Dequeue(); 256 int index = operators.IndexOf(cur.ToString()); 257 258 if (index >= 0) 259 { 260 right = operandStack.Pop(); 261 left = operandStack.Pop(); 262 operandStack.Push(Compute(left, right, index)); 263 } 264 else 265 { 266 operandStack.Push(cur.ToString()); 267 } 268 } 269 return operandStack.Pop(); 270 } 271 } 272 }