Menu

自动生成人中学型迷你学数学标题标垄断台小程序,总计表明式



前段时间写了一个自动生成中小学数学题目的控制台小程序

用户需求:

下面简要介绍一下我这次的一个开发过程

程序能接收用户输入的整数答案,并判断对错
程序结束时,统计出答对、答错的题目数量。
补充说明:0——10的整数是随机生成的
用户可以选择四则运算中的一种
用户可以结束程序的运行,并显示统计结果。
在此基础上,做增量开发。

需求:

增量内容:
1)处理用户的错误输入,比如输入字母或符号等,处理除法运算中分母为0的情况,处理结果为负数的情况,保证是小学水平不出现负数,比如不能出现5-8=-3这种情况;
              2)用户可以设定倒计时;
              3)用户可以设定随机整数的范围和题目数量;
             
4)用户可以选择哪种计算类型,比如加减乘除,或可选择软件随机生成四则运算中的一种;
              5)用户可以选择随机生成的题目中是否带有小括号,比如(2+3)*5,如果是gui程序,添加这个功能可以用复选框实现。
             
6)保证生成过的题目不再重复出现。

1、命令行输入用户名和密码,如果用户名和密码都正确,提示输入小学、初中和高中三个选项中的一个,否则提示“请输入正确的用户名、密码”;

设计思路:

2、命令行输入小学、初中和高中的选项之一,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;

           
 因为是做增量啊,所以呢,我在看了5和6这两个增量之后,第一感觉是有点难度。这次和之前的运算不同的地方是:
           
 对于5):加了小括号,这就要考虑优先级了,并且不能再用两个文本框来生成两个数字进行运算了。所以我考虑,把两个文本框合成一个文本框,这样的话就让它来存生成的一个式子,最后只对这个式子进行运算就OK了,这不但可以生成两个数的运算表达式,还可以生成三个数的运算表达式,就看你怎么定义出题方法了。当然了,想的是很简单,毕竟生成的是一个式子而并非两个数进行计算那么简单了。所以我继续分析,就对后面的这个式子进行研究啊。首先它是一个字符串表达式,而进行计算的话肯定不能用string类型的来进行计算。所以用队列把表达式里的每一个数取出来,并定义一个栈,让取出的元素放到栈里,这样的话只对栈里元素用逆波兰式进行计算就可以了。
             1.对于实现
出题为一个字符串表达式,我用了如下这种方法:

3、程序读取输入正确的小学、初中和高中三个选项中一个后,控制台提示,“准备生成XX数学题目”(xx为输入的小学、初中和高中三个中的一个)。同时提示“请输入生成题目数量”;

                            n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
                            n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
                            int n3 = ran.Next(1, int.Parse(textBox5.Text));
                            textBox1.Text += "(";
                            if (n1 < 0) textBox1.Text += "(" + n1 + ")";
                            else textBox1.Text += n1;
                            if (ran.Next(0, 2) == 1) textBox1.Text += "+";
                            else textBox1.Text += "-";
                            if (n2 < 0) textBox1.Text += "(" + n2 + ")";
                            else textBox1.Text += n2 + ")";
                            if (ran.Next(0, 2) == 1) textBox1.Text += "*"+n3;
                            else textBox1.Text += "/" + n3;
                            break;

4、题目数量的有效输入范围是“10-30”,程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复;

           
 2.对于让它连续出一种运算的表达式,根据最小取值范围的字符长度,也就是截取第一个运算数后面的运算符来进行判断。如下:

5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存。每道题目有题号,每题之间空一行;

                    if (s.Substring(textBox4.TextLength, 1) == "+")
                    {
                        RandomNumjia();
                    }

实现思路:

             3.在准备工作做好之后就是进行计算了:
图片 1

首先这个项目的核心部分也是最有挑战性的部分就是出题的部分了,其他的登录和难度选择只需要普通的选择循环就可以搞定。所以我选择主攻这里

代码实现:

随机一道题需要什么呢?

Form1.cs

一道最简单的算式是这样的:3×5+4=19

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 namespace _Random
 11 {
 12     public partial class Form1 : Form
 13     {
 14         public Form1()
 15         {
 16             InitializeComponent();
 17         }
 18         public static int select = 0;
 19         public static int Count = 0;
 20         private int t = 60;
 21         public static int right = 0;
 22 
 23         private void button1_Click(object sender, EventArgs e)
 24         {
 25             label2.Text = t.ToString();
 26             timer1.Enabled = true;
 27             timer1.Interval = 1000;
 28             timer1.Start();
 29         }
 30 
 31         private void RDN()
 32         {
 33             Random ran=new Random();
 34             int n1,n2;
 35             if (textBox4.Text==""&&textBox5.Text=="")
 36             {
 37                 MessageBox.Show("请输入取值范围!");
 38                 return;
 39             }
 40             if (checkBox1.Checked == true)
 41                 select = 1;
 42             for (int i = 0; i < int.Parse(textBox6.Text); i++)
 43             {
 44                 textBox1.Clear();
 45                 switch (select)
 46                 {
 47                     case 1:
 48                         {
 49                             n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
 50                             n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
 51                             int n3 = ran.Next(1, int.Parse(textBox5.Text));
 52                             textBox1.Text += "(";
 53                             if (n1 < 0) textBox1.Text += "(" + n1 + ")";
 54                             else textBox1.Text += n1;
 55                             if (ran.Next(0, 2) == 1) textBox1.Text += "+";
 56                             else textBox1.Text += "-";
 57                             if (n2 < 0) textBox1.Text += "(" + n2 + ")";
 58                             else textBox1.Text += n2 + ")";
 59                             if (ran.Next(0, 2) == 1) textBox1.Text += "*"+n3;
 60                             else textBox1.Text += "/" + n3;
 61                             break;
 62                         }
 63                 }
 64                 
 65                 textBox3.Text = "";
 66             }                
 67         }
 68 
 69         private void RandomNumjia()
 70         {
 71             textBox1.Clear();
 72             textBox3.Clear();
 73             if (textBox4.Text == "" && textBox5.Text == "")
 74             {
 75                 MessageBox.Show("请输入取值范围!");
 76                 return;
 77             }
 78 
 79             Random ran = new Random();
 80             int n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
 81             int n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
 82             if (n1 < 0) textBox1.Text += "(" + n1 + ")";
 83             else textBox1.Text += n1;
 84             textBox1.Text += "+";
 85             if (n2 < 0) textBox1.Text += "(" + n2 + ")";
 86             else textBox1.Text += n2; 
 87         }
 88 
 89         private void RandomNumjian()
 90         {
 91             textBox1.Clear();
 92             textBox3.Clear();
 93             if (textBox4.Text == "" && textBox5.Text == "")
 94             {
 95                 MessageBox.Show("请输入取值范围!");
 96                 return;
 97             }
 98 
 99             Random ran = new Random();
100             int n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
101             int n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
102             if (n1 < 0) textBox1.Text += "(" + n1 + ")";
103             else textBox1.Text += n1;
104             textBox1.Text += "-";
105             if (n2 < 0) textBox1.Text += "(" + n2 + ")";
106             else textBox1.Text += n2;
107         }
108 
109         private void RandomNumcheng()
110         {
111             textBox1.Clear();
112             textBox3.Clear();
113             if (textBox4.Text == "" && textBox5.Text == "")
114             {
115                 MessageBox.Show("请输入取值范围!");
116                 return;
117             }
118 
119             Random ran = new Random();
120             int n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
121             int n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
122             if (n1 < 0) textBox1.Text += "(" + n1 + ")";
123             else textBox1.Text += n1;
124             textBox1.Text += "*";
125             if (n2 < 0) textBox1.Text += "(" + n2 + ")";
126             else textBox1.Text += n2;
127         }
128 
129         private void RandomNumchu()
130         {
131             textBox1.Clear();
132             textBox3.Clear();
133             if (textBox4.Text == "" && textBox5.Text == "")
134             {
135                 MessageBox.Show("请输入取值范围!");
136                 return;
137             }
138 
139             Random ran = new Random();
140             int n1 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
141             int n2 = ran.Next(int.Parse(textBox4.Text), int.Parse(textBox5.Text));
142             if (n1 < 0) textBox1.Text += "(" + n1 + ")";
143             else textBox1.Text += n1;
144             textBox1.Text += "/";
145             if (n2 < 0) textBox1.Text += "(" + n2 + ")";
146             else textBox1.Text += n2;
147         }
148 
149         private void timer1_Tick(object sender, EventArgs e)
150         {
151             if (t <= 0)
152             {
153                 timer1.Enabled = false;
154                 textBox3.Enabled = false;
155                 MessageBox.Show("时间到!");
156                 textBox3.Enabled = false;
157                 Form2 frm2 = new Form2();
158                 frm2.ShowDialog();
159             }
160             t = t - 1;
161             label2.Text = t.ToString();
162         }
163 
164         private void button2_Click(object sender, EventArgs e)
165         {
166             timer1.Stop();
167             Form2 frm2 = new Form2();
168             frm2.ShowDialog();
169         }
170 
171         private void button3_Click(object sender, EventArgs e)
172         {
173             RandomNumjia();
174         }
175 
176         private void button4_Click(object sender, EventArgs e)
177         {
178             RandomNumjian();
179         }
180 
181         private void button5_Click(object sender, EventArgs e)
182         {
183             RandomNumcheng();
184         }
185 
186         private void button6_Click(object sender, EventArgs e)
187         {
188             RandomNumchu();
189         }
190 
191         private void button7_Click(object sender, EventArgs e)
192         {
193             if (textBox4.Text == "" && textBox5.Text == "")
194             {
195                 MessageBox.Show("请输入取值范围!");
196                 return;
197             }
198             else
199             {
200                 for (int i = 0; i < int.Parse(textBox6.Text);i++)
201                 {
202                     RDN();
203                 }
204             }
205         }
206 
207         private void textBox3_KeyDown(object sender, KeyEventArgs e)
208         {
209             string result = textBox1.Text;
210 
211             if (Count == int.Parse(textBox6.Text))
212             {
213                 Form2 frm2 = new Form2();
214                 frm2.ShowDialog();
215             }
216 
217             if (e.KeyCode == Keys.Enter)
218             {
219                 if (textBox3.Text == Calucate(result).ToString())   //直接调用Calucate这个方法计算result的值并与输入的值进行比较
220                 {
221                     right++;
222                     Count++;
223                     MessageBox.Show("回答正确!");
224                 }
225 
226                 else
227                 {
228                     MessageBox.Show("答题错误!");
229                     Count++;
230                     string s = textBox1.Text;
231                     if (s.Substring(textBox4.TextLength, 1) == "+")
232                     {
233                         RandomNumjia();
234                     }
235                     else if (s.Substring(textBox4.TextLength, 1) == "-")
236                     {
237                         RandomNumjian();
238                     }
239                     else if (s.Substring(textBox4.TextLength, 1) == "*")
240                     {
241                         RandomNumcheng();
242                     }
243                     else if (s.Substring(textBox4.TextLength, 1) == "/")
244                     {
245                         RandomNumchu();
246                     }
247                     else if (s.Length > 5)
248                     {
249                         RDN();
250                     }
251                 }
252 
253                 string m = textBox1.Text;
254                 if (m.Substring(textBox4.TextLength, 1) == "+")
255                 {
256                     RandomNumjia();
257                 }
258                 else if (m.Substring(1, 1) == "-")
259                 {
260                     RandomNumjian();
261                 }
262                 else if (m.Substring(1, 1) == "*")
263                 {
264                     RandomNumcheng();
265                 }
266                 else if (m.Substring(1, 1) == "/")
267                 {
268                     RandomNumchu();
269                 }
270                 else if (m.Length > 5)
271                 {
272                     RDN();
273                 }
274             }
275         }
276 
277         const string operators = "+-*/";                 //运算符
278         static Dictionary<char, int> priorities = null;  //优先级
279 
280         static void Calculator()                         //添加了四种运算符以及四种运算符的优先级
281         {
282             priorities = new Dictionary<char, int>();    
283             priorities.Add('#', -1);                     
284             priorities.Add('+', 0);
285             priorities.Add('-', 0);
286             priorities.Add('*', 1);
287             priorities.Add('/', 1);
288         } 
289 
290         static int Compute(int leftNum, int rightNum, char op)  //这是一种方法,用来计算左右两个数的静态方法!
291         {
292             switch (op)
293             {
294                 case '+': return leftNum + rightNum;
295                 case '-': return leftNum - rightNum;
296                 case '*': return leftNum * rightNum;
297                 case '/': return leftNum / rightNum;
298                 default: return 0;
299             }
300         }
301 
302         static bool IsOperator(char op)                  //每次判断这个字符是否是运算符?
303         {
304             return operators.IndexOf(op) >= 0;
305         }
306 
307         static bool IsAssoc(char op)                //返回一个关联符号
308         {
309             return op == '+' || op == '-' || op == '*' || op == '/';
310         }
311 
312         static Queue<object> QueueSort (string expression)           // 队列排序   
313         {
314             Queue<object> result = new Queue<object>();             
315             Stack<char> operatorStack = new Stack<char>();           //运算符栈
316             operatorStack.Push('#');
317             char top, cur, tempChar;                                          //top栈顶,current最近的;
318             string tempNum;
319             for (int i = 0, j; i < expression.Length; )                 //取出表达式
320             {
321                 cur = expression[i++];                                  //取出表达式的每个字符赋给cur
322                 top = operatorStack.Peek();                             //栈顶元素赋给top此时为"#"
323 
324                 if (cur == '(')                                         //将左括号压栈,此时栈顶元素为"("
325                 {
326                     operatorStack.Push(cur);
327                 }
328                 else
329                 {
330                     if (IsOperator(cur))                             //如果是运算符的话
331                     {
332                         while (IsOperator(top) && ((IsAssoc(cur) && priorities[cur] <= priorities[top])) || (!IsAssoc(cur) && priorities[cur] < priorities[top]))
333                         {
334                             result.Enqueue(operatorStack.Pop());     //如果元素为运算符并且优先级小于栈顶元素优先级,出栈
335                             top = operatorStack.Peek();              //继续把栈顶元素赋给top
336                         }
337                         operatorStack.Push(cur);                     //把数字压栈
338                     }
339                     else if (cur == ')')                           //将右括号添加到结尾
340                     {
341                         while (operatorStack.Count > 0 && (tempChar = operatorStack.Pop()) != '(')
342                         {
343                             result.Enqueue(tempChar);
344                         }
345                     }
346                     else
347                     {
348                         tempNum = "" + cur;
349                         j = i;
350                         while (j < expression.Length && (expression[j] == '.' || (expression[j] >= '0' && expression[j] <= '9')))
351                         {
352                             tempNum += expression[j++];
353                         }
354                         i = j;
355                         result.Enqueue(tempNum);
356                     }
357                 }
358             }
359             while (operatorStack.Count > 0)
360             {
361                 cur = operatorStack.Pop();
362                 if (cur == '#') continue;
363                 if (operatorStack.Count > 0)
364                 {
365                     top = operatorStack.Peek();
366                 }
367 
368                 result.Enqueue(cur);
369             }
370 
371             return result;
372         }
373 
374         static int Calucate(string expression)
375         {
376             try
377             {
378                 var rpn = QueueSort(expression);                   //rpn逆波兰表达式reverse polish notation 
379                 Stack<int> operandStack = new Stack<int>();
380                 int left, right;
381                 object cur;
382                 while (rpn.Count > 0)
383                 {
384                     cur = rpn.Dequeue();                           //出列
385                     if (cur is char)                               //如果cur为字符的话
386                     {
387                         right = operandStack.Pop();                //右边的数字出栈
388                         left = operandStack.Pop();                 //左边的数字出栈
389                         operandStack.Push(Compute(left, right, (char)cur));    //此时调用compute方法
390                     }
391                     else
392                     {
393                         operandStack.Push(int.Parse(cur.ToString()));            //是数字就压栈
394                     }
395                 }
396                 return operandStack.Pop();
397             }
398             catch
399             {
400                 throw new Exception("表达式格式不正确!");
401             }
402         }
403 
404     }
405 }

抛开后面的答案来看,一道算式的组成由3个数字和两个运算符组成,那么我就可以先随机产生这道题一共有几个数字,假设有n个数字,那么就会有n-1个运算符,我可以随机生成这些数字和字符。

 

好了,如果把一道题看成一道菜的话,那么我的材料已经准备好了!然后开始做菜,将这些数字和字母按照数字——字母——数字的顺序去拼接起来。这时,一个最简单的算式就这样生成了。

代码编写过程:

进阶!

图片 2

可以看到需求中我们还要对难度进行划分,需要加入平方,开方,三角函数等运算符,怎么加入呢?最开始我的想法和和之前一样,去随机数字以后随机运算符,但是这是会碰到一个问题,如果还是按照之前的方法,就会产生这样的算式:14sin5cos3
,很显然这个式子是有问题的。我也与同学进行了讨论,我的观念中是存在14sin5
这样的式子的,但是我忽略了一点很重要的就是我们平常算题的时候经常会省略乘号,如果很正式的写法的话是不能忽略的。退一步来讲,就算
14sin5 成立,如果按照这种方法出题,根本不会出现 14+sin5
这样的式子,所以我需要改变我的出题方法,于是我对随机方法进行了一次处理,如果随机的到特殊运算符,那我就给他前面补充随机一个普通运算符,这样就很好的完成了出题的式子,很开心的看到类似这样的式子14+sin5-cos3

Form2.cs

此时你是否感觉已经大功告成了?不,这会还有一个很重要的问题没有处理掉,仔细想想,有没有这样第一个很神奇的运算符,他叫做平方,他不出现在数字前面,而是在数字后面,实践一下,会出现这样的式子:14+²38
,哈哈。对于这一点我的处理方法是检测式子中的 ²
符号,如果检测到这个字符且他下一位是一个数字,就交换两个字符的位置,然后继续向后比较,就可以很轻松的把式子调整为14+38²
。(但是这会并不是调整的最佳时机,稍后会提到)

 

进阶+1!

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Windows.Forms;
 9 
10 namespace _Random
11 {
12     public partial class Form2 : Form
13     {
14         public Form2()
15         {
16             InitializeComponent();
17         }
18 
19         private void Form2_Load(object sender, EventArgs e)
20         {
21             textBox1.Text = Form1.Count.ToString();
22             textBox2.Text = Form1.right.ToString();
23             textBox3.Text = (Form1.Count - Form1.right).ToString();
24         }
25 
26     }
27 }

普通的式子已经可以很完美的生成了。但是括号我们还没有加进去,这一点困扰了我很久,怎么加括号比较好,我开始想到了逆波兰表达式(又称后缀表达式,我们平常使用的是中缀表达式),考虑到学习的时间成本的问题(其实我就是想偷懒QwQ,但是后来项目二次改进的时候我还是重新学了一遍后缀表达式),后来我又按照之前的思路思考,一个算式写好后,最多可以插入几组括号呢?写一个式子看看咯,首先
2+2=4 这个式子不需要括号。那么三个数字的呢?2+3+5=10
这个可以插入一组括号,以此类推,不难发现括号组数是数字个数n-2。那就好办了啊。我随机
n-2 次然后随机判断每次是否插入。然后开始插入,插入 (
的时候只能插入到数字前面,插入 )
的时候只能插入到数字后面,这一点是肯定的。其次,要先插入(
,然后标记插入只能插在这之后。整体这么插下来以后基本还算好吧。但是会出现括号包含整个算式的情况,两组括号甚至多组括号重叠的情况)
需要去重),还有只包围一个数字的情况。这时我没有从插入括号的机制上去进行判断,感觉工作量会很大,我这会对出来的式子进行了一次清洗工作,如果碰到这三种式子就直接扔掉重新生成。

 运行过程:

好了!算式搞定了!这会我们要写入到试卷中了。

答题的时候给出取值范围,和预想答题数目,然后点击随机,这时程序就会生成一个式子。
当我在输入答案并回车的时候会对生成的式子进行计算,并与输入的答案进行比较。

直接写入就好了呀,然后写入完存到文件里,文件的命名很简单,但是要注意每个用户的试卷要分开存放,便于提取其中的内容,这里我才用的做法是在一个papers的文件夹下给每个用户都有一个独立的文件夹去存储试卷。然后出题的时候读取这个用户之前所有的题目进行一个查重工作,防止重复。

图片 3

最后的就是整个系统的流程控制,流程控制这里我最开始想的是利用循环和选择来进行,后来发现循环写多了以后会很乱,可能会出现6-7层while循环嵌套的情况。不利于阅读与后续的维护。这会我采用了函数的方式去处理了这个问题。

 当时间用完,或者点击结束运算的时候,会弹出测试结果并提示时间到。

下面展示一下函数表

图片 4

Demo.main //主函数
Demo.menu() //主菜单
Demo.login() //登录界面
menu2(String, String) //二级菜单
selectlevel(String, String) //难度选择
paper_generate(String, int, String) //试卷生成
problem_generate //题目生成
brackets //加括号
isRight //合理性检测
itemIsExist(String, String) //存在性检测

 PSP耗时分析:
图片 5

大致就是函数直接互相调用来解决了多层循环嵌套的问题吧。感觉会更好一些,对于开发和后期维护来说,思路上更清晰一些,很多东西找起来也更快一些。同时功能模块的复用性很好,在二次改进的项目中我又直接调用了这次写好的函数模块。

结对编程总结:

然后这个程序我写了一个简单的用户类,其实并没有起到什么作用,只是开始的时候觉得面对对象编程需要这样一个对象吧。后来证明很需要这个用户类,在下一篇博客中会提到。

说明:我们一起做了1个增量

最后展示一下程序使用还有源码吧。

5)用户可以选择随机生成的题目中是否带有小括号,比如(2+3)*5。

实现效果:

 

图片 6

这次结对编程呢,还是和李燕燕一块的。总体来说,这次作业对我们来说确实有难度,因为那个逆波兰式我之前根本没有听说过,等到用到的时候才发现自己学到的知识真是太少了。我之前是觉得只要学到老师教的东西就行了,其实并不是,老师教的东西远远不够,想要使自己的编程能力提高,还是要靠我们自己学习。我们学了一种语言C#,没人会再教我们第二种语言,那么这第二种、第三种语言…需要我们自己来学了。比如这次逆波兰式我不会了,我就上网查啊查,折腾着,也想过要放弃不做了,但是我是那种看到别人能做出来相信自己也能做出来的人。所以我先是编写了一个控制台的小程序,一点一点测试,慢慢再加入到窗体,直到最后测试通过。才发现原来我是可以的!我觉得这种自主学习的方式我一定要坚持下去!通过这次结对编程,我认为:自主学习比较重要!!!

图片 7

图片 8

源码:

标签:,

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图