个人作业1——四则运算题目生成程序(基于控制台)
一、需求分析
- 生成四则运算题目
- 控制生成题目个数
- 控制生成题目中数字的范围
- 结果为真分数
- 每道题目运算符个数为3
- 每次运行生成的题目不能重复
- 保存生成的题目
- 在生成题目的同时,计算出所有题目的答案,并保存
- 支持一万道题目的生成
- 支持题目判错,并保存统计结果
二、功能设计
- 基本功能
- 每个数字定义为一个包含分子、分母、符号的结构体
- 随机生成数字
- 随机生成四则运算符号
- 按照所有可能出现的情况随机生成式子
- 在重新定义的数字上实现通分、约分、以及加减乘除的基本操作
- 将生成的式子转化成后缀表达式
- 通过后缀表达式计算式子的值
- 再生成式子时进行查重
- 将生成的式子以及计算出的答案保存到txt文件
- 对提供的式子和答案进行打分校验,包括查重,并将统计结果保存到txt文件
- 通过命令行传递参数
- 扩展功能
- 解决加括号的意义问题(即括号内的符号至少有一个+或-)
- 完善命令行传递参数的各种情况处理,并提供帮助选项
- 对结果为负数的情况进行了处理
三、设计实现
- 我的整体思路是将一个数看做一个有分子、分母和符号的结构体,然后在这个基础上进行生成和各种运算。数字在内存中一律是真分数,只有在需要将其输出成字符串时,再将其转换成对应的整数、带分数、真分数。而加上一个名为符号的成员变量是为了处理运算结果出现负数的情况。
- 关于查重功能的实现,我分为两种。一种是在生成式子时的查重,此时只需要将式子对应的后缀表达式进行排序后存到一个map<string, int>中,其key为后缀表达式排序后的字符串,并将其值置为1,之后遇到计算结果相同的式子只需要判断其后缀表达式排序后的字符串对应的map中的值是否为1即可知道是否重复。另一种是在对用户提供的式子进行查重时,判断的方式与上面相同;区别在于由于需要获得两个重复式子的位置和内容,所以需要将前一个式子的位置信息和自身提前存起来,等后一个式子判断与其重复时,再将其信息返回,这样就可以将两个式子同时存到另一个地方,最后一起输出。
- 项目主要包括Math.h、Calculator.h、PrintAndFile.h三个头文件
- Math.h主要封装了数字结构体的定义和一些相关的计算函数
- Calculator.h主要封装了生成数字、生成运算符、生成式子、将式子转换成后缀表达式和计算后缀表达式的值等函数
- PrintAndFile.h主要封装了打印命令行提示以及一些读文件和存文件的相关函数,但是不知道为什么一把它分出来就会编译出错,所以就只好把它写在main函数里面了
四、代码说明
- 生成数字的代码如下:
//生成Number Number GenerateNumber(int numMax){ int isInt, RealNumerator, IntNumber; Number num; num.sign = 1; isInt = rand() % 2; if (isInt){ num.denominator = 1; num.numerator = rand() % numMax + 1; } else{ num.denominator = rand() % numMax + 2; RealNumerator = rand() % (num.denominator - 1) + 1; //约分 int common = CommonDivisor(num.denominator, RealNumerator); num.denominator = num.denominator / common; RealNumerator = RealNumerator / common; IntNumber = rand() % (numMax - 1); num.numerator = IntNumber*num.denominator + RealNumerator; } return num; }
- 将字符串转换为Number的代码如下,主要思想是通过定位特殊符号的位置来进行转换:
//将字符串转为Number Number Str2Num(string str){ Number num; int optPosi[2] = { 0, 0 }; for (int i = 0; i < str.size(); i++){ if (str[i] == 39){ optPosi[0] = i; } else if (str[i] == 47){ optPosi[1] = i; } } if (optPosi[0] == 0 && optPosi[1] == 0){ int tem = 0; for (int i = str.size(), j = 0; i > 0; i--, j++){ tem = tem + (str[j] - 48) * pow(10, i - 1); } num.numerator = tem; num.denominator = 1; num.sign = 1; return num; } else if (optPosi[0] == 0 && optPosi[1] != 0){ //有 真分数 1/2 int tem = 0; for (int i = optPosi[1], j = 0; i > 0; i--, j++){ tem = tem + (str[j] - 48) * pow(10, optPosi[1] - j - 1); } num.numerator = tem; tem = 0; for (int i = str.size(), j = optPosi[1] + 1; i > j; i--, j++){ tem = tem + (str[j] - 48) * pow(10, str.size() - j - 1); } num.denominator = tem; num.sign = 1; } else{ //有带分数 1‘2/3 int tem1 = 0, tem2 = 0, temInt = 0; for (int i = optPosi[0], j = 0; i > 0; i--, j++){ temInt = temInt + (str[j] - 48) * pow(10, i - 1); } temInt = temInt; for (int i = optPosi[1], j = optPosi[0] + 1; i >= j; i--, j++){ tem1 = tem1 + (str[j] - 48) * pow(10, optPosi[1] - j - 1); } for (int i = str.size(), j = optPosi[1] + 1; i >= j; i--, j++){ tem2 = tem2 + (str[j] - 48) * pow(10, str.size() - j - 1); } num.denominator = tem2; num.numerator = temInt * tem2 + tem1; num.sign = 1; } return num; }
- 查重的代码如下:
//校验时查重 bool Check(string que, string queStr, map<string, int> &queList, map<string, Question> &QueList, int posi, Question &Que){ Question queStruct; sort(que.begin(), que.begin() + que.size()); if (queList.empty() || queList[que] == NULL){ queList[que] = 1; queStruct.no = posi; queStruct.queStr = queStr; QueList[que] = queStruct; return false; } else{ Que = QueList[que]; return true; } } //生成时查重 bool Check(string que, map<string, int> &queList){ sort(que.begin(), que.begin() + que.size()); if (queList.empty() || queList[que] != 1){ queList[que] = 1; return false; } else{ return true; } }
- 将式子转换成后缀表达式的代码如下,是通过栈实现的,通过比较不同符号栈内和栈外的优先级来决定是入栈还是出栈:
//生成后缀表达式 void GeneratePostfix(string que, list<string> &quePostfix, stack<char> &optStack){ map<char, int> isp, icp; isp['+'] = 3; isp['-'] = 3; isp['*'] = 5; isp['/'] = 5; isp['('] = 1; isp[')'] = 6; icp['+'] = 2; icp['-'] = 2; icp['*'] = 4; icp['/'] = 4; icp['('] = 6; icp[')'] = 1; isp['#'] = 0; icp['#'] = 0; string temPostfix; char optNow; que = que + '#'; optStack.push('#'); for (int i = 0; i < que.size(); i++){ if (que[i] > 47 && que[i] < 58){ temPostfix = temPostfix + que[i]; if (que[i + 1] == 39){ temPostfix = temPostfix + que[i + 1] + que[i + 2] + que[i + 3] + que[i + 4]; i = i + 4; } else if (que[i + 1] == 47){ temPostfix = temPostfix + que[i + 1] + que[i + 2]; i = i + 2; } if (que[i + 1] < 48){ temPostfix = temPostfix + " "; } continue; } else if (que[i] < 0){ optNow = '/'; i++; } else{ optNow = que[i]; } while (isp[optStack.top()] > icp[optNow]){ if (optStack.top() == 47){ temPostfix = temPostfix + "÷ "; } else{ temPostfix = temPostfix + optStack.top() + " "; } optStack.pop(); } if (isp[optStack.top()] == icp[optNow]){ optStack.pop(); } else if (isp[optStack.top()] < icp[optNow]){ optStack.push(optNow); } } quePostfix.push_back(temPostfix); }
- 计算表达式的代码如下,也是通过栈来实现的:
//计算表达式 void Calculate(string que, list<string> &ans, stack<Number> &ansStack, bool isError){ Number num; string temStr; char optNow; for (int i = 0; i < que.size(); i++){ while (que[i] != ' '){ temStr = temStr + que[i]; i++; } if (que[i] = ' '){ if (temStr[0] > 47 && temStr[0] < 58){ num = Str2Num(temStr); ansStack.push(num); temStr = ""; continue; } else if (temStr[0] < 0){ Number num1, num2; //除法 num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Division(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 42){ //乘法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Multiplication(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 43){ //加法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Addition(num1, num2); ansStack.push(num); temStr = ""; } else if (temStr[0] == 45){ //减法 Number num1, num2; num2 = ansStack.top(); ansStack.pop(); num1 = ansStack.top(); ansStack.pop(); num = Subtraction(num1, num2); ansStack.push(num); temStr = ""; } } } if (ansStack.top().denominator == 0){ isError = true; } else{ string a = Num2Str(ansStack.top()); ans.push_back(a); ansStack.pop(); } }
五、测试运行
- 命令行参数提示
- 参数出错提示
-
进行出题
-
- 打分校验结果
六、PSP展示
PSP2.1 |
Personal Software Process Stages |
Time Senior Student |
Time |
Planning |
计划 |
10 |
7 |
· Estimate |
估计这个任务需要多少时间 |
10 |
7 |
Development |
开发 |
325 |
903 |
· Analysis |
需求分析 (包括学习新技术) |
10 |
20 |
· Design Spec |
生成设计文档 |
5 |
0 |
· Design Review |
设计复审 |
5 |
0 |
· Coding Standard |
代码规范 |
5 |
3 |
· Design |
具体设计 |
60 |
30 |
· Coding |
具体编码 |
300 |
655 |
· Code Review |
代码复审 |
10 |
5 |
· Test |
测试(自我测试,修改代码,提交修改) |
30 |
190 |
Reporting |
报告 |
60 |
60 |
· |
测试报告 |
40 |
Nah |
七、小结
这次作业第一次开始接触到PSP来管理项目开发,让我在开发过程中效率提高了不少,虽然预估和实际差的还是挺大,但是相对自己之前而言,效率已经提高了不少,之后我会尽可能把这种方式运用到其实事情上来提高自己效率。
此外这次作业的内容看起来并不是很难,但是真正做起来的时候还是很花时间的,尤其是在一些细节的地方,其实有一大部分时间都是在调试、debug,还好最终问题都得到了解决,最后的效果也算是比较完整,个人感觉比较有缺陷的还是代码的质量上,感觉自己写的代码质量不高,一些东西没有设计好,希望以后可以不断提高自己的代码质量。
最后附上代码地址:https://coding.net/u/DaleAG/p/Calculator/git/tree/master/