高级软件工程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 };
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 };
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 }
自动生成整数,分数,运算符和括号。并将每个对象以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 }
计算式子的最后结果,输入是存放在队列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,如果不符合就会被丢弃,这就导致每次生成一个满足要求的式子会有点慢,这个以后希望可以改进。