作业3:生成四则运算命令行程序-结对项目 - 作业
实现一个自动生成小学四则运算题目的命令行程序
作业所属课程 | 结对项目 - 作业 - 计科21级12班 |
---|---|
作业要求 | 结对项目 - 作业 - 计科21级12班 |
作业目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
结对项目:林智谦3121004831 叶飞池3121004843
github地址:Flychee/Flychee (github.com)
一、题目要求
-
使用
-n
参数控制生成题目的个数,例如Myapp.exe -n 10
-
使用
-r
参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如Myapp.exe -r 10
-
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
-
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
-
每道题目中出现的运算符个数不超过3个。
-
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
-
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
四则运算题目1
四则运算题目2
.....
-
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8
-
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
- 答案1
- 答案2
-
程序应能支持一万道题目的生成。
-
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
二、效能分析
程序整体算法复杂度是 $O(n^2)$,即生成一次算式遍历已生成的算式,查询是否重复,由于生成查询比较和转化为表达式树的不是完全的 $O(1)$,所以常数比较大。
在最初版本中,没有将表达式树存下来,判重的时候要重复建树导致复杂度飙升,O(n^2(m+m+m+m)),生成、建树、比较、计算答案的复杂度都是O(m),m指的是算式的长度。按这种方式生成1万道题需要26分钟。
接着将树按数组存下来,直接比较两个数组判重,生成1000道需要2.4s,1万需要110s。
三、设计实现过程
生成步骤:
- 按输入参数首先随机生成算式。
- 按规则生成表达式树,(按符号、子树深度、数字大小交换左右子树)
- 枚举已经生成的所有表达式树,判重。
- 如果无重复且答案非负数,则生成答案。
答案校对步骤:
- 输入答案。
- 逐个比较。
- 输出正确和错误的题号。
四、代码说明
1.命令行参数读取
使用了 Python 内置的 argparse
模块来解析命令行参数。创建一个 ArgumentParser
对象 parser
,用来定义命令行参数。然后,我们使用 add_argument()
方法来添加四个命令行参数:
-r
或--radius
:用来指定生成数值的范围,默认值为 10。-n
或--num
:用来指定生成题目的数量,默认值为 10。-e
或--exercise
:用来指定生成的题目文件名。-a
或--answer
:用来指定生成的答案文件名。
parser = argparse.ArgumentParser()
parser.add_argument('-r', "--radius", type=int, default=10)
parser.add_argument('-n', "--num", type=int, default=10)
parser.add_argument('-e', "--exercise", type=str)
parser.add_argument('-a', "--answer", type=str)
args = parser.parse_args()
2.题目生成
使用伪代码描述整个生成过程:
function question(num, radius, exercise_file, answer_file):
# 初始化题目和答案列表
question_result = []
answer_result = []
# 循环生成 num 道题目
for i in range(num):
# 生成随机数和运算符序列
temp = num_random(radius)
oper = oper_random()
# 处理运算符序列,生成新的表达式列表
temp_result = cal_seq(oper, temp)
# 将表达式列表转换为二叉树,并计算二叉树的值
tree = buildTree(temp_result)
value = evaluate(tree)
# 判断当前表达式是否与之前的表达式重复
if not is_duplicate(temp_result, question_result):
# 将表达式和答案添加到题目和答案列表中
question_result.append(temp_result)
answer_result.append(value)
# 将题目和答案写入文件
write_file(exercise_file, question_result)
write_file(answer_file, answer_result)
3.判重
需求描述的判重并非单纯的字符串判断,对于计算顺序也不能重复,期初想到的是后缀表达式,判重后使用中缀表达式输出;但是一个后缀表达式不能以一定的规则使其唯一。最后发现表达式树按一定规则生成的话,一个算式对应唯一的表达式树,表达式树不相同,则算式不同。
五、测试运行
1.基本功能
测试1:生成10以内的四则运算。.\Myapp.exe -n 10 -r 10
测试2:生成100以内的四则运算。.\Myapp.exe -n 10 -r 100
测试3:生成5以内的四则运算,判断是否重复。.\Myapp.exe -n 20 -r 5
测试4:生成1000道题。.\Myapp.exe -n 1000 -r 100
测试5:生成10000道题。.\Myapp.exe -n 10000 -r 100
2.校对答案
Exercises.txt存的是题目;Answers,txt存答案;Grade.txt存结果。
.\Myapp.exe -e Exercises.txt -a Answers.txt
测试1:(整数答案)2题的答案是错的,最后的校对成功校对出错误答案。
测试2:(分数答案)
测试3:(带分数答案)
测试4:(多个答案错误)
测试5:(全对)
测试6:(全错)
六、项目小结
人员分工:林智谦主要负责需求分析、算法分析,思考完成方案、测试代码,同时负责撰写博客;叶飞池主要负责代码的编写。本次项目,林智谦同学对c++和算法比较熟悉,叶飞池同学对python比较熟悉,所以由叶飞池同学主要编写代码。
遗憾:对于一棵表达式树,因为是二叉树,所以可以使用一维数组存储,然后计算哈希值,这样判重的时候就只需要$O(1)$。由于后面没时间改了,所以作罢。
七、附录
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 300 | 180 |
· Design Spec | · 生成设计文档 | 120 | 30 |
· Design Review | · 设计复审 | 60 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 180 | 180 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 180 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 120 | 60 |
· Size Measurement | · 计算工作量 | 20 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
· 合计 | 1230 | 800 |