高级软件工程2017第2次作业—— 个人项目:四则运算题目生成程序(基于控制台)

 

1、题目描述

 
     从《构建之法》第一章的 “程序” 例子出发,完成一个能自动生成小学四则运算题目的命令行 “软件”,满足以下需求:
 
     1、参与运算的操作数(operands)除了100以内的整数以外,还要支持真分数的四则运算,例如:1/6 + 1/8
    =7/24。操作必须随机生成。
     2、运算符(operators)为 +, −, ×, ÷ (如运算符个数固定,则不得小于3)运算符的种类和顺序必须随机生成。
     3、要求能处理用户的输入,并判断对错,打分统计正确率。
     4、使用 -n 参数控制生成题目的个数,例如执行下面命令将生成5个题目
          (以C/C++/C#为例) calgen.exe -n 5
          (以python为例) python3 calgen.py -n 5
       附加功能(算附加分)
            1、支持带括号的多元复合运算
            2、运算符个数随机生成(考虑小学生运算复杂度,范围在1~10)
 

2、解题思路

 
  • 我的整体思路是将所有对象看做是一个有分子、分母和符号的结构体,分数表示成分子、分母和“|”符号,整数表示成分子为整数,分母为1和符号为“|”,加法表示成分子为0,分母为1和符号为“+”,减法表示成分子为0,分母为1和符号为“-”,......,都是以这种形式在栈和队列中表示。只有当要输出的时候才转换成原来的形式。
 
  • 对生成的运算式子进行控制。在生成整数时用rand()函数控制生成的数在100范围内,对分数,只有当分子和分母都在1~10范围内且分子小于分母才有效,否则重新生成分数。对于每次生成的式子都会用额外的队列存储,当运算的最后结果不为负数且分母和分子都不大于100时,这个式子才有效可以显示出来,否则丢掉重新生成。
    
  • 项目主要包含math.h和calgen.cpp两个文件。
 
  • math.h主要封装了结构体的定义和一系列对分数运算的函数。
 
  • calgen.cpp主要包含生成数字,生成分数,生成运算符,生成式子,将式子转换成后缀表达式,计算结果等函数。
 

3.代码说明

    1、Fraction结构体

1 struct Fraction {
2     int deno; //Denominator
3     int numer; //Numerator
4     char symbol;//Operator
5 };
View Code

    2、FractionCalculate类

 

 1 class FractionCalculate {
 2 public:
 3     int gcd(int a, int b) //Maximum common divisor
 4     {
 5         int temp, r;
 6         if (a < b) {
 7             temp = a;
 8             a = b;
 9             b = temp;
10         }
11         while (b != 0) {
12             r = a % b;
13             a = b;
14             b = r;
15         }
16         return a;
17     }
18     Fraction FractionAdd(Fraction f1, Fraction f2)//Addition
19     {
20         Fraction f3;
21         f3.deno = f1.deno * f2.deno;
22         f3.numer = f1.numer * f2.deno + f1.deno * f2.numer;
23         return Fractionsim(f3);
24     }
25     Fraction Fractionsub(Fraction f1, Fraction f2)//Subtraction
26     {
27         Fraction f3;
28         f3.deno = f1.deno * f2.deno;
29         f3.numer = f1.numer * f2.deno - f1.deno * f2.numer;
30         return Fractionsim(f3);
31     }
32     Fraction Fractionmul(Fraction f1, Fraction f2)//Multiplication
33     {
34         Fraction f3;
35         f3.deno = f1.deno * f2.deno;
36         f3.numer = f1.numer * f2.numer;
37         return Fractionsim(f3);
38     }
39     Fraction Fractiondiv(Fraction f1, Fraction f2)//Division
40     {
41         Fraction f3;
42         f3.numer = f1.numer * f2.deno;
43         f3.deno = f1.deno * f2.numer;
44         return Fractionsim(f3);
45     }
46     Fraction Fractionsim(Fraction f1)//Fractional simplification
47     {
48         int temp = gcd(f1.deno, f1.numer);
49         f1.deno /= temp;
50         f1.numer /= temp;
51         return f1;
52     }
53 };
View Code

 

 3、Generate函数

 1 void Generate(int n)
 2 {
 3     for (int i = 1;i <= n;i++)
 4     {
 5         int k = i;
 6         srand((unsigned)time(0));
 7         int operatorsnum = rand() % 12 + 3;//Generate the number of operands 
 8         int bracketsid = rand() % 2;
 9         int bracketnum = 0; //The pair of brackets
10         if (bracketsid == 0)//Generate left bracks
11         {
12             Fraction fc;
13             fc.deno = 1;
14             fc.numer = 0;
15             fc.symbol = '(';
16             space.push_back(fc);
17             line.push_back(fc);
18             bracketnum++;
19         }
20 
21         random();
22 
23         for (int j = 0;j < operatorsnum;j++)
24         {
25             Generateoperators();
26 
27             bracketsid = rand() % 2;
28             if (bracketsid == 0 && j<operatorsnum - 1)
29             {
30                 Fraction fc;
31                 fc.deno = 1;
32                 fc.numer = 0;
33                 fc.symbol = '(';
34                 space.push_back(fc);
35                 line.push_back(fc);
36                 bracketnum++;
37                 random();
38             }
39             else
40             {
41                 random();
42                 if (bracketnum > 0 && bracketsid == 1)
43                 {
44                     Fraction fc;
45                     fc.deno = 1;
46                     fc.numer = 0;
47                     fc.symbol = ')';
48                     space.push_back(fc);
49                     line.push_back(fc);
50                     bracketnum--;
51                 }
52             }
53         }
54         while (bracketnum > 0)
55         {
56             Fraction fc;
57             fc.deno = 1;
58             fc.numer = 0;
59             fc.symbol = ')';
60             space.push_back(fc);
61             line.push_back(fc);
62             bracketnum--;
63         }
64         Answer(k);
65         if (temp == 1)
66         {
67             i = i - 1;
68             temp = 0;
69         }
70     }
71 }
View Code

  自动生成整数,分数,运算符和括号。并将每个对象以Fraction结构体的方式存储在space队列中。

 4、计算

 

  1 bool isBracket(char c)//Determine whether it is brackets
  2 {
  3     if (c == '(' || c == ')')
  4         return true;
  5     else
  6         return false;
  7 }
  8 
  9 
 10 int getPri(char c)//Get the priority of the symbol
 11 {
 12     switch (c)
 13     {
 14     case '+':
 15     case '-':
 16         return 0;   //If it is addition and subtraction, return 0  
 17         break;
 18     case '*':
 19     case '/':
 20         return 1;   //If it is multiplied and division, returns 1 
 21         break;
 22     case '(':
 23     case ')':
 24         return -1;  //Set brackets to the lowest priority 
 25         break;
 26     default:
 27         break;
 28     }
 29 }
 30 
 31 int checkzero(Fraction op1) //Check whether the divisor is zero
 32 {
 33     if (op1.numer == 0)
 34     {
 35         return 1;
 36     }
 37     else
 38     {
 39         return 0;
 40     }
 41 }
 42 
 43 void check(Fraction c, stack<Fraction>& space2, deque<Fraction>& space3)//Determine the priority of the symbol
 44 {
 45     if (space2.empty())
 46     {
 47         space2.push(c);
 48         return;
 49     }
 50 
 51     if (isBracket(c.symbol))
 52     {
 53         if (c.symbol == '(')
 54             space2.push(c);
 55         else
 56         {
 57             while (space2.top().symbol != '(') //Pop all elements until the left parenthesis is encountered
 58             {
 59                 Fraction ch = space2.top();
 60                 space3.push_back(ch);
 61                 space2.pop();
 62             }
 63 
 64             space2.pop();
 65         }
 66     }
 67     else
 68     {
 69         Fraction sym = space2.top();
 70 
 71         if (getPri(c.symbol) <= getPri(sym.symbol)) //Compare the priority of two symbols 
 72         {
 73             //If c's priority is smaller than or equal to the top of the stack, the stack top element is popped  
 74             space2.pop();
 75             //push it into space3 (suffix expression)  
 76             space3.push_back(sym);
 77             check(c, space2, space3);
 78         }
 79         else
 80         {
 81             //If c is greater than the top of the stack, it will push c into space2 (operator stack) 
 82             space2.push(c);
 83         }
 84     }
 85 }
 86 
 87 //Remove the element from space1 and assign the element to space2 and space3  
 88 void allocate(deque<Fraction>& space1, stack<Fraction>& space2, deque<Fraction>& space3)
 89 {
 90     while (!space1.empty())
 91     {
 92         Fraction c = space1.front();
 93         space1.pop_front();
 94 
 95         if (c.symbol == '|')
 96         {
 97             space3.push_back(c);
 98         }
 99         else
100         {
101             check(c, space2, space3);
102         }
103 
104     }
105 
106     //If the input ends, the space2 element will pop up and join the suffix expression  
107     while (!space2.empty())
108     {
109         Fraction c = space2.top();
110         space3.push_back(c);
111         space2.pop();
112     }
113 }
114 
115 //Calculate the suffix expression 
116 Fraction calculate(deque<Fraction> space1)
117 {
118     stack<Fraction> space2;
119     deque<Fraction> space3;
120     stack<Fraction> space4;
121     allocate(space1, space2, space3);
122     while (!space3.empty())
123     {
124         Fraction c = space3.front();
125         space3.pop_front();
126 
127         //If it is an operand, press it into the space4 stack  
128         if (c.symbol == '|')
129         {
130             space4.push(c);
131         }
132         else     //If it is an operator, pop up the elements from the stack to calculate  
133         {
134             Fraction op1 = space4.top();
135             space4.pop();
136             Fraction op2 = space4.top();
137             space4.pop();
138             FractionCalculate fc;
139             if (c.symbol == '+')
140             {
141                 space4.push(fc.FractionAdd(op2, op1));
142             }
143             else if (c.symbol == '-')
144             {
145                 space4.push(fc.Fractionsub(op2, op1));
146             }
147             else if (c.symbol == '*')
148             {
149                 space4.push(fc.Fractionmul(op2, op1));
150             }
151             else
152             {
153                 if (c.symbol == '/')
154                 {
155                     int t=checkzero(op1);
156                     if (t == 0)
157                     {
158                         space4.push(fc.Fractiondiv(op2, op1));
159                     }
160                     else
161                     {
162                         Fraction fg;
163                         fg.deno = 0;
164                         fg.numer = 1;
165                         fg.symbol = '|';
166                         return fg;
167                     }
168                 }
169             }
170         }
171     }
172     return space4.top();
173 }
View Code

 

 

 

  计算式子的最后结果,输入是存放在队列space中的中缀表达式,转换成后缀表达式后计算结果。

 4、测试运行

 1、单元测试

  第一次使用单元测试,主要对FractionCalculate类中的成员函数进行了一些测试。

  

 2、运行

  可以随机生成括号和运算符(不会超过10个)。

  

 5、PSP

psp Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 10
· Estimate · 估计这个任务需要多少时间 10 10
Development 开发 485 645
· Analysis · 需求分析 (包括学习新技术) 30 40
· Design Spec · 生成设计文档 20 30
· Design Review · 设计复审 (和同事审核设计文档) 5 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 30 45
· Coding · 具体编码 180 210
· Code Review · 代码复审 30 60
· Test · 测试(自我测试,修改代码,提交修改) 180 240
Reporting · 报告 210 240
· Test Report · 测试报告 150 180
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
合计   705

895

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6、小结

  第一次使用PSP表格记录下整个项目的预计用时和实际用时,让我对项目开发的整体过程有了一个基本的理解,并且能够直观的显示时间具体花在什么地方,对提高编程效率有一定的帮助。整个项目在调试bug的过程是最占用时间的,第一次使用单元测试,确实很好用在,比自己调试要方便很多。整个项目并不难,但处理细节部分还是很麻烦。在项目中用了一个辅助队列来存储每次生成算式,但由于有分数的乘法和除法,最后的结果会有负数和很大的一些分子和分母,所以有限制最后的结果不为负数和分子,分母都不大于100,如果不符合就会被丢弃,这就导致每次生成一个满足要求的式子会有点慢,这个以后希望可以改进。

posted @ 2017-09-25 20:40  whu_mashuai(马帅)  阅读(287)  评论(5编辑  收藏  举报