结对项目——四则运算
这个作业属于哪个课程 | 软工22级计科12班 |
---|---|
这个作业的要求在哪里 | 作业要求 |
这个作业的目标 | 实现四则运算的结对编程 |
项目成员
姓名 | 学号 | GitHub链接 | 分工 |
---|---|---|---|
谭立业 | 3122004365 | github | 项目功能的基本实现,博客的编写 |
罗锴佳 | 3122001905 | github | 功能函数的测试与完善,异常处理,bug修复以及性能分析 |
PSP表格
1.基本功能
实现一个自动生成小学四则运算题目并且可以进行对答案进行检查的程序,可以使用命令行参数控制生成题目的个数,范围等。
2.项目开发
2.1.开发环境:
-
编程语言:Python
-
编程环境:Pycharm
2.2.项目结构:
2.3项目整体设计与实现过程:
2.4函数接口设计
函数接口 | 功能 | 参数解释 |
---|---|---|
Transform(list) | 将列表题目中的分数变成真分数 | list:题目列表 |
generate_random(r) | 生成随机数 | r:题目的范围 |
generate_one_bracket(list, f, s) | 对于一个题目列表生成一个括号 | list:题目列表,f,s: 一个括号插入的位置 |
generate_two_bracket(list, fir, sec, thi, four) | 对于一个题目列表生成两个括号 | list:题目列表 fir, sec, thi, four: 两个括号插入的位置 |
generate_parentheses(operator_count, list) | 随机生成括号 | list:题目列表 operator_count:运算符的个数 |
generate_space(list) | 将列表转化为算术表达式,并在每一项之间都添加空格 | list:题目列表 |
generate_exercise_answer(r, n) | 生成题目和答案文件 | r:题目的范围 n:题目的数目 |
generate_Grade(Exercise_File, Answer_File) | 答案检查 | Exercise_File:题目文件路径 Answer_File:答案文件路径 |
main() | 主函数 | 通过命令行输入参数 |
2.6函数调用关系
2.7主要算法代码及说明
主要算法:generate_exercise_answer(r, n):题目与答案文件的生成
点击查看代码
def generate_exercise_answer(r, n):
# r为输入的范围,n为题目的个数
# questions用来存放算术表达式
continue_time = 0
questions = []
questions_F = []
# 定义列表来存放答案
answers = []
# 生成运算式
operators = ['+', '-', '×', '÷']
allOperators = ['+', '-', '×', '÷', '(', ')']
# 通过循环的方式随机生成算数表达式
while len(answers) < n:
# 此处的意思为随机产生运算符的个数
operator_count = random.randint(1, 3)
# 当为一个运算符的时候
if operator_count == 1:
list = []
# 生成两个随机数和一个随机运算符
count1 = generate_random(r)
count2 = generate_random(r)
operator = random.choice(operators)
# 存放在列表中
list.append(count1)
list.append(operator)
list.append(count2)
# 随即生成括号构造算数表达式
list = generate_parentheses(operator_count, list)
# 将假分数转换为真分数
list_temp = Transform(list)
# 生成算术表达式
question_F = generate_space(list)
# 生成真分数表达式
question_T = generate_space(list_temp)
temp = question_F.replace('×', '*').replace('÷', '/')
# 异常处理,若出现除以0的情况,直接跳过后续步骤并开始新一轮的生成
try:
answer = Fraction(eval(temp)).limit_denominator()
except ZeroDivisionError:
continue
# 如果答案的结果大于给定的范围,或者为负数,那么该情况不合理,直接跳过后续步骤并开始新一轮的生成
if answer < 0 or answer > r:
continue
# 将答案转换为字符串
answer_temp = str(answer)
# 将符合条件的算数表达式以及答案存入questions和answers列表中
questions.append(question_T)
questions_F.append(question_F)
answers.append(answer_temp)
# 当运算符为两个时
if operator_count == 2:
list = []
count1 = generate_random(r)
count2 = generate_random(r)
count3 = generate_random(r)
operator1 = random.choice(operators)
operator2 = random.choice(operators)
list.append(count1)
list.append(operator1)
list.append(count2)
list.append(operator2)
list.append(count3)
# 为列表添加括号
list = generate_parentheses(operator_count, list)
list_temp = Transform(list)
# 为原假分数生成算数表达式
question_F = generate_space(list)
# 为真分数生成算数表达式
question_T = generate_space(list_temp)
temp = question_F.replace('×', '*').replace('÷', '/')
# 异常处理,若出现除以0的情况,直接跳过后续步骤并开始新一轮的生成
try:
answer = Fraction(eval(temp)).limit_denominator()
except ZeroDivisionError:
continue
# 如果答案的结果大于给定的范围,或者为负数,那么该情况不合理,直接跳过后续步骤并开始新一轮的生成
if answer < 0 or answer > r:
continue
answer_temp = str(answer)
questions.append(question_T)
questions_F.append(question_F)
answers.append(answer_temp)
else:
list = []
count1 = generate_random(r)
count2 = generate_random(r)
count3 = generate_random(r)
count4 = generate_random(r)
operator1 = str(random.choice(operators))
operator2 = str(random.choice(operators))
operator3 = str(random.choice(operators))
list.append(count1)
list.append(operator1)
list.append(count2)
list.append(operator2)
list.append(count3)
list.append(operator3)
list.append(count4)
# 为列表添加括号
list = generate_parentheses(operator_count, list)
# 将假分数转换为真分数
list_temp = Transform(list)
# 生成算术表达式
question_F = generate_space(list)
# 生成真分数表达式
question_T = generate_space(list_temp)
# 异常处理,若出现除以0的情况,直接跳过后续步骤并开始新一轮的生成
temp = question_F.replace('×', '*').replace('÷', '/')
try:
answer = Fraction(eval(temp)).limit_denominator()
except ZeroDivisionError:
continue
# 如果答案的结果大于给定的范围,或者为负数,那么该情况不合理,直接跳过后续步骤并开始新一轮的生成
if answer < 0 or answer > r or answer.denominator > 10:
continue
answer_temp = str(answer)
# 判断跟新生成的表达式与之前的表达式是否等价。
# 1.如果生成的answer值和之前answers钟所存的值有相等的
# 2.同时,新生成的题目通过去括号重新排序后与之前相等的
question_Switch = False
answer_Switch = False
questions_temp = []
# 对questions的quetsions变成可以比较是否等价的1样子
for i in range(len(questions_F)):
questions_temp.append(''.join(sorted(questions_F[i].replace('(', '').replace(')', '').replace(' ', ''))))
if answer_temp in answers:
answer_Switch = True
if ''.join(sorted(question_F.replace('(', '').replace(')', '').replace(' ', ''))) in questions_temp:
question_Switch = True
if question_Switch == True and answer_Switch == True:
continue_time += 1
continue
# 防止生成的式子等价太多
if continue_time >= 10000:
raise Exception("提供的范围过小")
questions.append(question_T)
questions_F.append(question_F)
answers.append(answer_temp)
# 将questions 和 answers 写入文件
questions_New = []
for i in range(len(questions)):
data = '四则运算题目' + str(i + 1) + '. ' + questions[i] + '\n'
questions_New.append(data)
answers_New = []
for i in range(len(answers)):
# 将答案列表中的假分数转换为真分数输出
temp = Fraction(eval(answers[i])).limit_denominator()
if temp.denominator != 1 and temp.numerator > temp.denominator:
ans = '答案' + str(i + 1) + ". " + str(temp.numerator // temp.denominator) + "'" + str(
temp - temp.numerator // temp.denominator) + '\n'
answers_New.append(ans)
else:
ans = '答案' + str(i + 1) + ". " + str(temp) + '\n'
answers_New.append(ans)
# 将列表中得到内容存入到.txt文件中
fp1 = open('Exercises.txt', 'w')
fp1.writelines(questions_New)
fp2 = open('Answers.txt', 'w')
fp2.writelines(answers_New)
fp1.close()
fp2.close()
该函数可以生成n个范围在r内的题目以及这些题目的答案,首先定义三个列表,生成过程中的中间列表,最终记录题目和答案的列表,通过给定的范围,产生符合范围的随机的运算符的个数,进而产生随机数和括号,用pycharm的fraction库的eval()来计算字符串表达式,得出结果,接着对结果进行检查是否合法,最后检查是否有重复的题目,如果上述问题都解决,将这一次产生的题目和结果存入题目文件和答案文件路径中。若其中有一项不符合则直接跳过,以此类推产生n个符合要求的题目和答案。
3.异常测试
3.1命令行输入参数异常:
①输入的命令不是-r -n 或者 -a -e则抛出异常
点击查看代码
else:
raise Exception(
'请按以下格式输入:'
'python Main.py -n [int] -r[int] '
'或 '
'python Main.py -e [str] -a [''str]')
测试结果:
②参数输入为负数,抛出异常
点击查看代码
if args.n < 0 or args.r < 0:
raise Exception('输入的参数为负数,请重新输入')
测试结果:
③提供的文件路径不存在,抛出异常
点击查看代码
if not os.path.exists(Exercise_File):
raise f'没有找到"{Exercise_File}" 文件'
if not os.path.exists(Answer_File):
raise f'没有找到"{Answer_File}" 文件'
测试结果:
3.2除零异常:
点击查看代码
try:
answer = Fraction(eval(temp)).limit_denominator()
except ZeroDivisionError:
continue
对于除以0的情况,抛出除零异常,该次出题作废,继续下一次出题。
3.3代码覆盖率
利用pycharm自带的coverage进行代码覆盖率的测试如下:
测试报告中显示,覆盖率为70%,Main中还有未被覆盖的代码,需要改进代码
3.4代码改进
在随机生成数字代码中发现,范围的设置有缺陷范围过大,导致最后的答案结果都为零,将生成的结果的分数的分母的范围缩小,防止生成随机数过小结算结果为零。
点击查看代码
up_limit = random.randint(0, r * r)
down_limit = random.randint(1, r)
result = Fraction(up_limit, down_limit).limit_denominator()
else:
result = random.randint(0, r)
if result > r:
return generate_random(r)
4.测试运行
命令行参数给出参数
①生成题目:
②题目文件:
③答案文件:
④答案检查:
⑤生成1万道题目
结果为: