项目需求:
像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”, 分别满足下面的各种需求。下面这些需求都可以用命令行参数的形式来指定:
a) 除了整数以外,还要支持真分数的四则运算。 (例如: 1/6 + 1/8 = 7/24)
b) 让程序能接受用户输入答案,并判定对错。 最后给出总共 对/错 的数量。
c) 逐步扩展功能和可以支持的表达式类型,最后希望能支持下面类型的题目 (最多 10 个运算符,括号的数量不限制):
25 - 3 * 4 - 2 / 2 + 89 = ?
1/2 + 1/3 - 1/4 = ?
(5 - 4 ) * (3 +28) =?
d) 一次可以批量出 100 道以上的题目,保存在文本文件中, 并且保证题目不能重复,(1+2) 和 (2+1) 是重复的题目。
我的实现:
我了解到我的同学们大多都是用前、中、后缀表达式的互相转换来实现算式计算的,这个算法在上学期的课中编写过了,所以我想用个不一样的方法实现。
我使用了C#的反射,动态生成、编译代码。这样,只要在程序中生成了中缀表达式,将其转换为String型然后插入动态代码中编译,即可直接算出结果。
代码动态创建及编译的核心代码:
1 // 1.CSharpCodePrivoder 2 CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); 3 4 // 2.ICodeComplier 5 ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler(); 6 7 // 3.CompilerParameters 8 CompilerParameters objCompilerParameters = new CompilerParameters(); 9 objCompilerParameters.ReferencedAssemblies.Add("System.dll"); 10 objCompilerParameters.GenerateExecutable = false; 11 objCompilerParameters.GenerateInMemory = true; 12 13 // 4.CompilerResults 14 GenerateLines(); 15 Console.WriteLine("==========No." + (i+1).ToString() + "=========="); 16 Console.WriteLine(lines); 17 18 CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, GenerateCode()); 19 20 if (cr.Errors.HasErrors) 21 { 22 Console.WriteLine("编译错误:"); 23 foreach (CompilerError err in cr.Errors) 24 { 25 Console.WriteLine(err.ErrorText); 26 } 27 } 28 else 29 { 30 //通过反射,调用HelloWorld的实例 31 Assembly objAssembly = cr.CompiledAssembly; 32 object objHelloWorld = objAssembly.CreateInstance("DynamicCodeGenerate.HelloWorld"); 33 MethodInfo objMI = objHelloWorld.GetType().GetMethod("OutPut"); 34 string re = objMI.Invoke(objHelloWorld, null).ToString(); 35 }
其中,GenerateCode()是我用来生成代码的函数,返回了一个需要被动态编译的代码,其中定义了namespace DynamicCodeGenerate,public class HelloWorld以及其下方法public string OutPut()。
代码生成函数GenerateCode()的主要功能即是生成算式。因为题目要求程序可以计算分数,所以GenerateCode()还需要能进行通分、约分的操作。
GenerateCode()函数的主要流程:生成随机数,产生一个中缀表达式;对表达式全通分(即每一项都乘上所有被除项之积),然后将得数与刚才乘上的数约分,即得到分数形式的答案。
之后接受用户输入,和标准答案比对后输出结果即可。
完整代码:
1 using System; 2 using System.Reflection; 3 using System.Globalization; 4 using Microsoft.CSharp; 5 using System.CodeDom; 6 using System.CodeDom.Compiler; 7 using System.Text; 8 using System.Collections.Generic; 9 using System.IO; 10 11 namespace A_Boring_Project_CSharp 12 { 13 class Program 14 { 15 static int max = 50; 16 static int rows = 10; 17 static List<int> nums; 18 static List<int> add_n_minus; 19 static List<int> div; 20 static List<int> div_nums; 21 static int tf; 22 static int amount; 23 static string lines; 24 static string lines_tf; 25 static string lines_final; 26 static int count; 27 28 static void Main(string[] args) 29 { 30 31 lines = ""; 32 count = 0; 33 34 for (int i = 0; i < rows; i++) 35 { 36 // 1.CSharpCodePrivoder 37 CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); 38 39 // 2.ICodeComplier 40 ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler(); 41 42 // 3.CompilerParameters 43 CompilerParameters objCompilerParameters = new CompilerParameters(); 44 objCompilerParameters.ReferencedAssemblies.Add("System.dll"); 45 objCompilerParameters.GenerateExecutable = false; 46 objCompilerParameters.GenerateInMemory = true; 47 48 // 4.CompilerResults 49 GenerateLines(); 50 Console.WriteLine("==========No." + (i+1).ToString() + "=========="); 51 Console.WriteLine(lines); 52 53 CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, GenerateCode()); 54 55 if (cr.Errors.HasErrors) 56 { 57 Console.WriteLine("编译错误:"); 58 foreach (CompilerError err in cr.Errors) 59 { 60 Console.WriteLine(err.ErrorText); 61 } 62 } 63 else 64 { 65 //通过反射,调用HelloWorld的实例 66 Assembly objAssembly = cr.CompiledAssembly; 67 object objHelloWorld = objAssembly.CreateInstance("DynamicCodeGenerate.HelloWorld"); 68 MethodInfo objMI = objHelloWorld.GetType().GetMethod("OutPut"); 69 string re = objMI.Invoke(objHelloWorld, null).ToString(); 70 71 string answer = Console.ReadLine(); 72 73 if (re == answer) 74 { 75 count++; 76 Console.WriteLine("Correct"); 77 } 78 else 79 { 80 Console.WriteLine("Wrong, the answer should be " + re.ToString()); 81 } 82 } 83 } 84 Console.WriteLine("Your score is: " + count.ToString() + " out of " + rows.ToString()); 85 Console.ReadLine(); 86 } 87 88 static string GenerateCode() 89 { 90 91 StringBuilder sb = new StringBuilder(); 92 sb.Append("using System;using System.IO;using System.Collections.Generic;using System.Text;"); 93 sb.Append(Environment.NewLine); 94 sb.Append("namespace DynamicCodeGenerate"); 95 sb.Append(Environment.NewLine); 96 sb.Append("{"); 97 sb.Append(Environment.NewLine); 98 sb.Append("\tpublic class HelloWorld"); 99 sb.Append(Environment.NewLine); 100 sb.Append("\t{"); 101 sb.Append(Environment.NewLine); 102 sb.Append("\tpublic string OutPut()"); 103 sb.Append(Environment.NewLine); 104 sb.Append("\t\t{"); 105 sb.Append(Environment.NewLine); 106 sb.Append("long tf = "+ tf + ";"); 107 sb.Append(Environment.NewLine); 108 sb.Append("long re = " + lines_final + ";"); 109 sb.Append(Environment.NewLine); 110 sb.Append("for (int i = 2; i < re || i < tf; i++){while (re % i == 0 && tf % i == 0){re = re / i;tf = tf / i;}}"); 111 sb.Append(Environment.NewLine); 112 sb.Append("Console.Write(\"Result = \");"); 113 sb.Append("\t\tif (tf != 1){return re.ToString() + \"/\" + tf.ToString();} else{return re.ToString();}"); 114 sb.Append(Environment.NewLine); 115 sb.Append(Environment.NewLine); 116 sb.Append("\t\t}"); 117 sb.Append(Environment.NewLine); 118 sb.Append("\t}"); 119 sb.Append(Environment.NewLine); 120 sb.Append("}"); 121 122 string code = sb.ToString(); 123 124 return code; 125 } 126 127 static void GenerateLines() 128 { 129 tf = 1; 130 nums = new List<int>(); 131 add_n_minus = new List<int>(); 132 div = new List<int>(); 133 div_nums = new List<int>(); 134 System.Random rnd = new System.Random(GetRandomSeed()); 135 amount = rnd.Next(1, 11); 136 lines = ""; 137 lines_tf = ""; 138 lines_final = ""; 139 140 int x = 0; 141 int d = 0; 142 List<int> anm; 143 List<int> op; 144 anm = new List<int>(); 145 op = new List<int>(); 146 for (int i = 0; i < amount; i++) 147 { 148 //A new number 149 rnd = new System.Random(GetRandomSeed()); 150 nums.Add(rnd.Next(1, max)); 151 lines += nums[i]; 152 //A new operator 153 System.Random rnd0 = new System.Random(GetRandomSeed()); 154 int key = rnd0.Next(0,4); 155 op.Add(lines.Length + 1); 156 switch (key) 157 { 158 case 0: 159 anm.Add(i); 160 x = 0; 161 lines += "+"; 162 add_n_minus.Add(lines.Length); 163 break; 164 case 1: 165 anm.Add(i); 166 x = 0; 167 lines += "-"; 168 add_n_minus.Add(lines.Length); 169 break; 170 case 2: 171 x++; 172 if (x <= 3) 173 { 174 lines += "*"; 175 break; 176 } 177 else 178 { 179 goto case 1; 180 } 181 case 3: 182 x = 0; 183 d++; 184 if (d <= 3) 185 { 186 lines += "/"; 187 div.Add(i); 188 break; 189 } 190 else 191 { 192 goto case 1; 193 } 194 default: 195 x = 0; 196 break; 197 } 198 } 199 op.Add(lines.Length); 200 201 rnd = new System.Random(GetRandomSeed()); 202 nums.Add(rnd.Next(1, max)); 203 lines += nums[amount]; 204 205 for (int i = 0; i < div.Count; i++) 206 { 207 div_nums.Add(nums[div[i] + 1]); 208 lines_tf += div_nums[i]; 209 lines_tf += "*"; 210 tf *= div_nums[i]; 211 } 212 213 lines_final += lines_tf; 214 lines_final += lines; 215 216 if (div.Count > 0) 217 { 218 for (int i = 1; i <= add_n_minus.Count; i++) 219 { 220 lines_final = lines_final.Insert(i * lines_tf.Length + add_n_minus[i - 1], lines_tf); 221 } 222 } 223 224 } 225 226 static int GetRandomSeed() 227 { 228 byte[] bytes = new byte[4]; 229 System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); 230 rng.GetBytes(bytes); 231 return BitConverter.ToInt32(bytes, 0); 232 } 233 234 } 235 }
小结:
在写GenerateCode()函数时,没有预先设计好控制、变量等等的具体实现,所以最终导致代码有点混乱,下次应该多加注意。