【个人项目总结】四则运算生成器

项目需求:

像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”, 分别满足下面的各种需求。下面这些需求都可以用命令行参数的形式来指定:

    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()函数时,没有预先设计好控制、变量等等的具体实现,所以最终导致代码有点混乱,下次应该多加注意。

原文地址:https://www.cnblogs.com/yuki8819/p/5305107.html