题目:实现一个自动生成小学四则运算题目的命令行程序
项目GitHub地址:https://github.com/LittleTaro/Calculator
结对人:舒雯钰(3218005129)、方子茵(3218005127)
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
45 |
· Estimate |
· 估计这个任务需要多少时间 |
30 |
35 |
Development |
开发 |
1670 |
2020 |
· Analysis |
· 需求分析 (包括学习新技术) |
240 |
520 |
· Design Spec |
· 生成设计文档 |
150 |
135 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
60 |
50 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
40 |
· Design |
· 具体设计 |
180 |
240 |
· Coding |
· 具体编码 |
600 |
600 |
· Code Review |
· 代码复审 |
180 |
135 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
240 |
300 |
Reporting |
报告 |
120 |
240 |
· Test Report |
· 测试报告 |
60 |
40 |
· Size Measurement |
· 计算工作量 |
30 |
45 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
65 |
合计 |
|
1820 |
2305 |
项目要求
使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
每道题目中出现的运算符个数不超过3个。
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:四则运算题目1
2. 四则运算题目2
3. ……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
答案1
答案2
……
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
程序应能支持一万道题目的生成。
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
效能分析
无
代码设计
关键代码
1 #include "Calculator.h" 2 //#include3 //#include 4 //#include 5 //#include 6 //#include 7 //using namespace std; 8 time_t g_unused = (srand(time(NULL)), 0); // Generate a seed for 'rand' in whole program. 9 10 const char* operators[4] = { " + ", " - ", " * ", " / " }; 11 12 int random(int a, int b) // 生成[a,b]间的随机整数 13 { 14 //srand((unsigned)time(NULL)); 15 return (rand() % (b - a + 1) + a); 16 } 17 18 char a[100] = ""; 19 char* op = a; // 存放转换成由数字转换而来的字符串 20 char b[100] = ""; 21 char* pro = b; // 存放题目 22 char c[100] = ""; 23 char* dem = c;// 存放转换成字符串的分母 24 char d[100] = ""; 25 char* tmp = d;;// 存放转换成字符串的带分数整数部分 26 27 28 char* GeneratePro() 29 { 30 int num = 0; // 运算符个数,1~3个 31 int index = 0; // 指示运算符的下标 32 num = random(1, 3); // 随机生成运算符个数,1到3个 33 int count = 0; 34 int left_c = 0; // 未匹配的左括号个数 35 int tag = 0; // 在操作数右边插入“)”指示该操作数左边是否插入左括号 36 memset(pro, 0, sizeof(pro)); // 每调用一次,都要初始化内存空间 37 //memset(tmp, 0, sizeof(tmp)); // 每调用一次,都要初始化内存空间 38 int way = 0; // 括号插入方式 39 while (count < num) 40 { 41 // 先在此处判断是否插入“(” 42 if (count == 0 && num > 1 && random(0, 1)) 43 { 44 strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 45 left_c++; // 未匹配的左括号个数加1 46 tag = 1; 47 } 48 else if (count == 1 && num > 1 && random(0, 1) && !left_c) 49 { 50 strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 51 left_c++; // 未匹配的左括号个数加1 52 tag = 1; 53 } 54 else if (count == 2 && num > 2 && random(0, 1) && !left_c) 55 { 56 strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 57 left_c++; // 未匹配的左括号个数加1 58 tag = 1; 59 } 60 // 随机生成一个运算数(整数或分数),并将其转变为字符串形式 61 op = GenerateOperand();//operand = GenerateOperand(); 62 strcat_s(pro, strlen(pro) + strlen(op) + 1, op); // 将操作符移到指向题目的指针 63 // 在此处判断是否插入“)” 64 if (tag != 1 && ((count == num - 1) || random(0, 1)) && left_c) 65 { 66 strcat_s(pro, strlen(pro) + 2, ")"); // 插入右括号 67 left_c--; // 未匹配的左括号个数减1 68 } 69 else if (tag == 1) 70 { 71 tag = 0; // 该数左边已插入"(",不可在右边插入")",但要取消标志以便下一次匹配 72 } 73 74 // 若第一循环到此处:pro是一个运算数字符串,下面再随机生成运算符并拼接 75 // 随机生成运算符下标 76 //srand((unsigned)time(NULL)); 77 index = rand() % 4; // 生成运算符索引下标 78 strcat_s(pro, strlen(pro) + 4, operators[index]); // 拼接运算数与运算符 79 count++; 80 } 81 // 此时还要再生成一个运算数并拼接 82 op = GenerateOperand(); 83 strcat_s(pro, strlen(pro) + strlen(op) + 1, op); 84 // 判断最后一个操作数后面是否插入“)” 85 if (left_c) 86 { 87 strcat_s(pro, strlen(pro) + 2, ")"); // 插入右括号 88 left_c--; // 未匹配的左括号个数减1 89 } 90 91 return pro; // 返回生成的题目 92 } 93 94 char* GenerateOperand() // 随机生成操作数 95 { 96 extern int range; // 引用main.cpp中的全局变量range,操作数范围 97 memset(op, 0, sizeof(op)); 98 int operand = 0; // 随机生成的整型运算数 99 int inteP = 0; // 带分数的整数部分 100 int demominator = 0; // 分母部分 101 int numerator = 0; // 分子部分 102 // 随机生成一个数,决定要生成整数(1),真分数(2),还是带分数(3) 103 int type = 0; // 即将生成的数据类型 104 //type = 1; 105 type = random(1, 3); 106 if (type == 1) 107 { 108 // 将生成整数 109 //srand((unsigned)time(NULL)); 110 operand = rand() % range; 111 _itoa_s(operand, op, 100, 10); //把整数部分转换成字符串 112 } 113 else 114 { 115 // 先生成不为0的且在范围内的分母 116 demominator = random(2, range - 1); 117 // 生成小于分母且非0的分子 118 numerator = random(1, demominator - 1); 119 _itoa_s(numerator, op, 100, 10); // 先把分子部分转换成字符串放入op 120 strcat_s(op, strlen(op) + 2, "/"); // 拼接分子串和"/" 121 _itoa_s(demominator, dem, 100, 10); // 122 strcat_s(op, strlen(op) + strlen(dem) + 1, dem); // 拼接上分母 123 // 到此op是一个表示真分数的字符串 124 // 下面判断是否要生成分数的整数部分 125 if (type == 3) // 将要生成带分数的整数部分 126 { 127 // 注意这个整数不能为0 128 inteP = random(1, range); // 生成范围内且非0的整数部分 129 memset(d, 0, sizeof(d)); 130 strcat_s(tmp, strlen(tmp) + strlen(op) + 1, op); // 将分数内容移交给tmp,以便op去接收整数部分 131 //tmp = op; // tmp寄存真分数部分 132 _itoa_s(inteP, op, 100, 10); // 将其转换成字符串放入op 133 strcat_s(op, strlen(op) + 2, "'"); // 拼接"'" 134 strcat_s(op, strlen(op) + strlen(tmp) + 1, tmp); // 拼接上真分数部分 135 } 136 } 137 return op; // 返回生成的操作数 138 }
计算逆波兰表达式
#include "Calculator.h" //#include//#include //#include //#include //#include //using namespace std; time_t g_unused = (srand(time(NULL)), 0); // Generate a seed for 'rand' in whole program. const char* operators[4] = { " + ", " - ", " * ", " / " }; int random(int a, int b) // 生成[a,b]间的随机整数 { //srand((unsigned)time(NULL)); return (rand() % (b - a + 1) + a); } char a[100] = ""; char* op = a; // 存放转换成由数字转换而来的字符串 char b[100] = ""; char* pro = b; // 存放题目 char c[100] = ""; char* dem = c;// 存放转换成字符串的分母 char d[100] = ""; char* tmp = d;;// 存放转换成字符串的带分数整数部分 char* GeneratePro() { int num = 0; // 运算符个数,1~3个 int index = 0; // 指示运算符的下标 num = random(1, 3); // 随机生成运算符个数,1到3个 int count = 0; int left_c = 0; // 未匹配的左括号个数 int tag = 0; // 在操作数右边插入“)”指示该操作数左边是否插入左括号 memset(pro, 0, sizeof(pro)); // 每调用一次,都要初始化内存空间 //memset(tmp, 0, sizeof(tmp)); // 每调用一次,都要初始化内存空间 int way = 0; // 括号插入方式 while (count < num) { // 先在此处判断是否插入“(” if (count == 0 && num > 1 && random(0, 1)) { strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 left_c++; // 未匹配的左括号个数加1 tag = 1; } else if (count == 1 && num > 1 && random(0, 1) && !left_c) { strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 left_c++; // 未匹配的左括号个数加1 tag = 1; } else if (count == 2 && num > 2 && random(0, 1) && !left_c) { strcat_s(pro, strlen(pro) + 2, "("); // 插入左括号 left_c++; // 未匹配的左括号个数加1 tag = 1; } // 随机生成一个运算数(整数或分数),并将其转变为字符串形式 op = GenerateOperand();//operand = GenerateOperand(); strcat_s(pro, strlen(pro) + strlen(op) + 1, op); // 将操作符移到指向题目的指针 // 在此处判断是否插入“)” if (tag != 1 && ((count == num - 1) || random(0, 1)) && left_c) { strcat_s(pro, strlen(pro) + 2, ")"); // 插入右括号 left_c--; // 未匹配的左括号个数减1 } else if (tag == 1) { tag = 0; // 该数左边已插入"(",不可在右边插入")",但要取消标志以便下一次匹配 } // 若第一循环到此处:pro是一个运算数字符串,下面再随机生成运算符并拼接 // 随机生成运算符下标 //srand((unsigned)time(NULL)); index = rand() % 4; // 生成运算符索引下标 strcat_s(pro, strlen(pro) + 4, operators[index]); // 拼接运算数与运算符 count++; } // 此时还要再生成一个运算数并拼接 op = GenerateOperand(); strcat_s(pro, strlen(pro) + strlen(op) + 1, op); // 判断最后一个操作数后面是否插入“)” if (left_c) { strcat_s(pro, strlen(pro) + 2, ")"); // 插入右括号 left_c--; // 未匹配的左括号个数减1 } return pro; // 返回生成的题目 } char* GenerateOperand() // 随机生成操作数 { extern int range; // 引用main.cpp中的全局变量range,操作数范围 memset(op, 0, sizeof(op)); int operand = 0; // 随机生成的整型运算数 int inteP = 0; // 带分数的整数部分 int demominator = 0; // 分母部分 int numerator = 0; // 分子部分 // 随机生成一个数,决定要生成整数(1),真分数(2),还是带分数(3) int type = 0; // 即将生成的数据类型 //type = 1; type = random(1, 3); if (type == 1) { // 将生成整数 //srand((unsigned)time(NULL)); operand = rand() % range; _itoa_s(operand, op, 100, 10); //把整数部分转换成字符串 } else { // 先生成不为0的且在范围内的分母 demominator = random(2, range - 1); // 生成小于分母且非0的分子 numerator = random(1, demominator - 1); _itoa_s(numerator, op, 100, 10); // 先把分子部分转换成字符串放入op strcat_s(op, strlen(op) + 2, "/"); // 拼接分子串和"/" _itoa_s(demominator, dem, 100, 10); // strcat_s(op, strlen(op) + strlen(dem) + 1, dem); // 拼接上分母 // 到此op是一个表示真分数的字符串 // 下面判断是否要生成分数的整数部分 if (type == 3) // 将要生成带分数的整数部分 { // 注意这个整数不能为0 inteP = random(1, range); // 生成范围内且非0的整数部分 memset(d, 0, sizeof(d)); strcat_s(tmp, strlen(tmp) + strlen(op) + 1, op); // 将分数内容移交给tmp,以便op去接收整数部分 //tmp = op; // tmp寄存真分数部分 _itoa_s(inteP, op, 100, 10); // 将其转换成字符串放入op strcat_s(op, strlen(op) + 2, "'"); // 拼接"'" strcat_s(op, strlen(op) + strlen(tmp) + 1, tmp); // 拼接上真分数部分 } } return op; // 返回生成的操作数
}
测试运行
项目小结
方子茵:
只实现了一小部分功能,因为沟通较少,不能充分了解对方的想法。没有详细计划好再编程。但是也从中意识自己能力有很大欠缺
结对项目双方要有充分交流,明确分工,了解彼此的想法,项目才能统一起来
结对感受:队友打码积极,但是和我一样比较拖拉,我们两个都不主动,结对项目要注重交流沟通
舒雯钰:
虽然很早就和队友沟通了想法和思路,但是比较晚开始具体编码,没有规划好时间,而且vscode的环境配置出了问题不能及时debug,我要对我的队友说抱歉QAQ。
结对感受:队友非常积极地沟通、安排进度,而且会主动说出具体的想法来对接,她负责的部分完成的很好。