成员:刘鸿杰 林程星
软件工程 | 计科21级4班 |
---|---|
作业要求 | 作业3要求连接 |
作业目标 | 实现四则运算题目的命令行程序 |
PSP表格
PSP2.1 | ersonal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 200 | 188 |
· Analysis | · 需求分析 (包括学习新技术) | 15 | 20 |
· Design Spec | · 生成设计文档 | 8 | 6 |
· Design Review | · 设计复审 | 3 | 3 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 3 | 3 |
· Design | · 具体设计 | 25 | 30 |
· Coding | · 具体编码 | 116 | 100 |
· Code Review | · 代码复审 | 10 | 6 |
· Test | · 测试(自我测试,修改代码,提交修改) | 20 | 20 |
Reporting | 报告 | 15 | 12 |
· Test Repor | · 测试报告 | 6 | 6 |
· Size Measurement | · 计算工作量 | 3 | 3 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 6 | 3 |
· 合计 | 225 | 210 |
需求描述
题目:实现一个自动生成小学四则运算题目的命令行程序
- 使用 -n 参数控制生成题目的个数
- 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,该参数必须给定,否则程序报错并给出帮助信息
- 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2
- 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数
- 每道题目中出现的运算符个数不超过3个
- 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目,生成的题目存入执行程序的当前目录下的Exercises.txt文件
- 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件
- 程序应能支持一万道题目的生成
- 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt
需求分析
题目分析
-
使用参数控制生成题目个数和数值范围
-
作业要求生成四则运算式,运算符最多三个,不能出现负数,分数必须是真分数,题目不能重复
-
将生成的算式,答案,判题结果都存储到文件中,支持一万道题目生成
涉及到程序参数获取,随机数生成,字符串拼接,数值校验,文件I/O流操作等,本次作业采用python完成,使用到argparse参数解析,re正则匹配,random随机数生成,Fraction分数计算
思路分析
- 使用随机数生成运算符个数和操作数,同时判断运算符为减号时,不能出现负数;运算符为除号时,不能出现假分数;将操作数和运算符拼接,并在随机位置插入括号,这样就可生成算式
- 计算算式的值时,先将运算符转换为计算机能识别的运算符,使用正则表达式匹配操作数,使用Fraction类将操作数分数化,便于结果的输出表示,再调用eval函数计算算式的答案
- 使用文件I/O流将算式和答案按要求的格式保存到指定文件中,当要校对答案时,将题目文件和答卷文件进行读取,将标准答案与输入答案比对,计算答对信息和答错信息,保存到指定成绩文件中
流程分析
详细设计
- 设计一个算式类Arithmetic,包含两个静态方法generate_arithmetic,calculate_arithmetic,分别用于生成算式,计算答案,在初始化方法里生成具体算式,对应答案,并赋值给formula,result属性
- 为了保存算式和答案,读取算式和答案,保存成绩信息,设计write_exercises,write_answer,read_exercises,read_answer,write_grade五个函数
- 设计exercises函数,实例化Arithmetic对象,根据题目数量和数值范围要求得到算式和答案,调用write_exercises,write_answer函数保存算式和答案
- 设计correct_answer函数,调用read_exercises函数和read_answer函数读取题目文件和答卷文件,调用Arithmetic类的静态方法calculate_arithmetic来计算答案,与答卷文件中的答案比对,统计答对与答错的信息,调用write_grade函数将统计结果保存到指定文件中
- 最后为了使用接收命令行参数实现功能,创建两个文件main_exercises和main_correct,使用argparse来解析命令行参数,然后分别调用moudle文件中的exercises函数和correct_answer函数,分开实现生成四则运算式和校对答案获取成绩信息两个大功能
关键代码
关键代码在于Arithmetic类,含有两个静态方法,generate_arithmetic和calculate_arithmetic,用于生成四则运算式和计算算式答案,主要运用到随机数生成,字符串拼接,正则表达式匹配等
# Arithmetic 算数类
class Arithmetic:
def __init__(self, r):
try:
if not isinstance(r, int) or r < 0:
raise TypeError
except TypeError:
print("输入的参数范围值必须是自然数")
self.result = -1
while self.result < 0:
self.formula = self.generate_arithmetic(r)
try:
self.result = self.calculate_arithmetic(self.formula)
except ZeroDivisionError:
continue
@staticmethod
def generate_arithmetic(r):
"""
:param r: 数字上限
:return: 字符串:算式
"""
num_operator = random.randint(1, 3)
operator_list = []
for _ in range(num_operator):
idx = random.randint(0, 3)
operator = '+-×÷'
operator_list.append(operator[idx])
operator_list.append('=')
# 生成运算数
number_list = []
for i in range(num_operator + 1):
number = random.randint(0, r)
if i != 0:
if operator_list[i - 1] == '-':
number = random.randint(0, int(number_list[i - 1]))
elif operator_list[i - 1] == '÷':
if number_list[i - 1] == 0:
number = random.randint(1, r)
else:
number = random.randint(int(number_list[i - 1]), r)
number_list.append(number)
# 加括号
idx_left_bracket = random.randint(0, len(number_list) - 2)
arithmetic = []
for i in range(len(number_list)):
if i == idx_left_bracket and num_operator > 1:
arithmetic.append('( ' + str(number_list[i]) + ' ' + operator_list[i] + ' ')
elif i == idx_left_bracket + 1 and num_operator > 1:
arithmetic.append(str(number_list[i]) + ' ) ' + operator_list[i] + ' ')
else:
arithmetic.append(str(number_list[i]) + ' ' + operator_list[i] + ' ')
arithmetic = ''.join(arithmetic)
return arithmetic
@staticmethod
def calculate_arithmetic(formula):
"""
:param formula: string:算式
:return: Fraction对象:答案
"""
# 将符号替换成python能识别的运算符
formula = formula.replace('=', '')
formula = formula.replace('×', '*')
formula = formula.replace('÷', '/')
formula = formula.replace(' ', '')
# 使用正则表达式将所有数字转换为Fraction类型,便于分数计算,再使用eval()函数计算字符串内的表达式
res = eval(re.sub(r'(\d+)', r'Fraction(\1)', formula))
return res
性能分析
性能分析主要包含对main_exercises.py和main_correct.py两个文件进行执行时间的分析,主要消耗在哪个函数或哪个模块,然后才能清楚性能瓶颈在哪,方便后期能够升级改进
- 首先分析main_exercises.py文件,该文件main方法主要是进行命令函参数解析,然后调用moudle中exercises函数根据指定范围生成默认10道题目,然后将题目和答案写入文件中
通过分析发现时间主要花费在moudle文件中的exercises函数,指定生成10000道题目,花费时间为1279ms,花费时间主要在calculate_arithmetic方法上,为642ms,执行eval表达式计算答案花费其中443ms
- 接着分析main_correct.py文件,该文件main方法主要是进行命令函参数解析,然后调用moudle中correct_answer函数读取题目文件和答卷文件,然后进行校对,统计答对和答错的信息,最后将成绩写入到指定文件中
通过分析发现时间主要花费在moudle文件中的correct_answer函数,测试时读取10000道题目和答案进行校对,且花费时间为569ms,花费时间主要在calculate_arithmetic函数,为404ms
测试运行
单元测试
为了对程序功能进行较为全面的测试,本次项目设计了10个测试用例,检测代码的健壮性,对程序实现进行较为全面的评估,从不同角度,不同函数进行测试分析
-
test_arithmetic_parameters:测试生成算式时参数范围不是自然数
# 测试生成算式时参数范围不是自然数 def test_arithmetic_parameters(self): modules.Arithmetic(-2)
-
test_generate_formular:测试生成10条算式是否符合要求
# 测试生成10条算式是否符合要求 def test_generate_formular(self): for _ in range(10): formular = modules.Arithmetic.generate_arithmetic(8) print(formular)
-
test_generate_enough_formular:测试能否生成一万条算式
# 测试能否生成一万条算式 def test_generate_enough_formular(self): for _ in range(10000): formular = modules.Arithmetic.generate_arithmetic(10) print(formular)
-
test_answer_express:测试文件内容分词效果
# 测试计算的答案表示是否符合要求 def test_answer_express(self): formular = '3 + 6 ÷ 7 =' answer = modules.Arithmetic.calculate_arithmetic(formular) print(formular, "", answer)
-
test_formular_answer:测试生成算式后计算的答案是否正确
# 测试生成算式后计算的答案是否正确 def test_formular_answer(self): for _ in range(10): formular = modules.Arithmetic.generate_arithmetic(8) answer = modules.Arithmetic.calculate_arithmetic(formular) print(formular, " ", answer)
-
test_write_file:测试将生成的题目和答案写入指定文件
# 测试将生成的题目和答案写入指定文件 def test_write_file(self): formular_list = [] answer_list = [] for _ in range(10): arithmetic = modules.Arithmetic(10) formular_list.append(arithmetic.formula) answer_list.append(arithmetic.result) modules.write_exercises(formular_list) modules.write_answer(answer_list)
-
test_read_not_exist_file:测试读取的题目文件和答案文件不存在
# 测试读取的题目文件和答案文件不存在 def test_read_not_exist_file(self): formular_list = modules.read_exercises("train.txt") answer_list = modules.read_answer("result.txt") print(formular_list) print(answer_list)
-
test_read_file:测试将题目文件和答案文件进行读取
# 测试将题目文件和答案文件进行读取 def test_read_file(self): formular_list = modules.read_exercises("Exercises.txt") answer_list = modules.read_answer("Answer.txt") print(formular_list) print(answer_list)
-
test_exercises:测试生成算式和计算相应答案,并写入到指定文件
# 测试生成算式和计算相应答案,并写入到指定文件 def test_exercises(self): modules.exercises(10, 10)
-
test_correct_answer:测试校对答案
# 测试校对答案 def test_correct_answer(self): modules.correct_answer("Exercises.txt", "Answer.txt")
测试结果
10个单元测试全部执行成功,全部花费时间为11.608s,其中test_generate_enough_formular单元测试为生成一万条算式,花费时间为11.579s
代码覆盖率
由于在文件I/O流等部分需要捕获处理异常,所以代码覆盖率为以上所显示
项目总结
- 结对编程体现了沟通交流,思想灵感碰撞的魅力与无限创新可能,在项目中互相讨论,提出自己的思路见解,不断完善设计规划,锻炼了沟通的能力,体现了协作交流的重要性
- 结对编程体现了合作的关键性,在项目经过需求分析和详细设计后,要进入真正实现的过程,对于程序的推进,需要明确各自的目标,尽可能达到最优的实现方案,完成负责的模块,提高开发效率
- 为完成项目需求,需要分析具体的实现方式来解决遇到的难题,学习新的思维模式,采用相关技术克服难题,在这个过程锻炼了逻辑思维,提升了编程能力