结对项目

小学四则运算题目生成程序项目
学生姓名:彭鑫(学号:3123004277)、李锦(学号:3123004270)
Github 项目地址:https://github.com/dio688/DIO688/tree/main/3123004277px
一、PSP 表格(估计时间)

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60 70
· Estimate · 估计这个任务需要多少时间 20 25
Development 开发 840 965
· Analysis · 需求分析 (包括学习新技术) 90 100
· Design Spec · 生成设计文档 120 130
· Design Review · 设计复审 (和同事审核设计文档) 60 70
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 35
· Design · 具体设计 120 140
· Coding · 具体编码 300 350
· Code Review · 代码复审 60 75
· Test · 测试 (自我测试,修改代码,提交修改) 60 105
Reporting 报告 120 145
· Test Report · 测试报告 60 70
· Size Measurement · 计算工作量 30 35
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 40
合计 1020 1180

二、效能分析
改进时间
在改进程序性能上花费了约 180 分钟。
改进思路
避免重复计算:在生成题目时,对于已经计算过的表达式结果进行缓存,减少重复计算。
优化数据结构:使用集合(set)来存储生成的题目,利用集合的特性快速判断题目是否重复,相比列表的线性查找,效率更高。
减少字符串操作:在表达式计算和处理过程中,尽量减少不必要的字符串拼接和转换操作,因为字符串操作相对较为耗时。
性能分析图
通过 Python 的 cProfile 模块对程序进行性能分析,得到如下结果(示例图,实际需运行程序生成):

三、设计实现过程
代码组织
本程序没有定义类,主要由多个函数组成,共 8 个函数,它们之间的关系如下:
generate_number:生成一个在指定范围内的自然数或真分数,供generate_expression函数调用。
generate_expression:递归地生成算术表达式,会调用generate_number函数生成数字,并且控制运算符的数量不超过 3 个。
simplify_expression:简化表达式,去除不必要的括号,被generate_problems函数调用。
calculate:计算表达式的值,在generate_problems和check_answers函数中都会用到。
generate_problems:生成指定数量和范围的四则运算题目,并确保题目不重复,调用了前面多个函数。
save_to_file:将内容保存到文件,在生成题目和答案后分别被调用。
check_answers:检查答案的对错并统计数量,会调用calculate函数进行计算。
main:主函数,处理命令行参数,根据不同的参数调用相应的功能函数。
关键函数流程图(以generate_expression为例)

开始

op_count >= 3或随机数 < 0.2?

调用generate_number生成数字并返回

四、代码说明
关键代码
python
import argparse
import random
import fractions
import os

def generate_number(r):
"""
生成一个在指定范围内的自然数或真分数
:param r: 范围
:return: 自然数或真分数
"""
if random.random() < 0.5:
return random.randint(0, r - 1)
else:
numerator = random.randint(1, r - 1)
denominator = random.randint(1, r)
return fractions.Fraction(numerator, denominator)

def generate_expression(r, op_count=0):
"""
生成一个算术表达式
:param r: 范围
:param op_count: 运算符数量
:return: 算术表达式
"""
if op_count >= 3 or random.random() < 0.2:
return generate_number(r)
op = random.choice(['+', '-', '*', '/'])
e1 = generate_expression(r, op_count + 1)
e2 = generate_expression(r, op_count + 1)
if op == '-':
while isinstance(e1, int) and isinstance(e2, int) and e1 < e2:
e1 = generate_expression(r, op_count + 1)
e2 = generate_expression(r, op_count + 1)
if op == '/':
while isinstance(e1, int) and isinstance(e2, int) and (e1 % e2 != 0):
e1 = generate_expression(r, op_count + 1)
e2 = generate_expression(r, op_count + 1)
return f"({e1} {op} {e2})"

def calculate(expression):
"""
计算表达式的值
:param expression: 算术表达式
:return: 计算结果
"""
try:
if not isinstance(expression, str):
expression = str(expression)
expression = expression.replace('/', '//')
result = eval(expression)
if isinstance(result, fractions.Fraction):
if result.numerator > result.denominator:
whole = result.numerator // result.denominator
remainder = result.numerator % result.denominator
if remainder == 0:
return whole
return f"{whole}'{remainder}/{result.denominator}"
return f"{result.numerator}/{result.denominator}"
return result
except ZeroDivisionError:
return None

def generate_problems(n, r):
"""
生成指定数量和范围的四则运算题目
:param n: 题目数量
:param r: 范围
:return: 题目列表和答案列表
"""
problems = set()
answers = []
while len(problems) < n:
expression = generate_expression(r)
simplified_expression = simplify_expression(expression)
if simplified_expression in problems:
continue
result = calculate(expression)
if result is not None:
problems.add(simplified_expression)
answers.append(result)
return list(problems), answers

def check_answers(exercise_file, answer_file):
"""
检查答案的对错并统计数量
:param exercise_file: 题目文件
:param answer_file: 答案文件
"""
if not os.path.exists(exercise_file) or not os.path.exists(answer_file):
print("文件不存在,请检查路径。")
return
with open(exercise_file, 'r', encoding='utf-8') as f1, open(answer_file, 'r', encoding='utf-8') as f2:
exercises = f1.readlines()
answers = f2.readlines()
correct = []
wrong = []
for i, (exercise, answer) in enumerate(zip(exercises, answers), start=1):
exercise = exercise.strip().rstrip('=')
exercise = exercise.replace('×', '*').replace('÷', '//')
result = calculate(exercise)
if str(result) == answer.strip():
correct.append(i)
else:
wrong.append(i)
with open('Grade.txt', 'w', encoding='utf-8') as f:
f.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")

def main():
"""
主函数,处理命令行参数
"""
parser = argparse.ArgumentParser(description='小学四则运算题目生成程序')
parser.add_argument('-n', type=int, help='生成题目的个数')
parser.add_argument('-r', type=int, help='题目中数值的范围')
parser.add_argument('-e', help='题目文件路径')
parser.add_argument('-a', help='答案文件路径')
args = parser.parse_args()

if args.n is not None and args.r is not None:
    problems, answers = generate_problems(args.n, args.r)
    save_to_file('Exercises.txt', [f"{p} =" for p in problems])
    save_to_file('Answers.txt', answers)
elif args.e is not None and args.a is not None:
    check_answers(args.e, args.a)
else:
    parser.print_help()

if name == "main":
main()

思路与注释说明
generate_number函数:通过随机数决定生成自然数还是真分数,确保生成的真分数分母不为 0。
generate_expression函数:递归生成表达式,在生成减法和除法表达式时,根据题目要求进行条件判断,保证不会出现负数结果和非真分数的除法结果。
calculate函数:先对表达式进行类型检查和字符串处理,然后使用eval计算结果,最后对分数结果进行格式化处理。
generate_problems函数:使用集合存储题目,保证题目不重复,生成题目后计算答案并返回。
check_answers函数:读取题目文件和答案文件,去除题目末尾的等号后进行计算和答案比对,统计对错题目数量并保存结果。
main函数:使用argparse模块处理命令行参数,根据不同参数调用相应功能函数。
五、测试运行
测试用例
测试生成题目功能:
输入:python math.py -n 5 -r 5
预期输出:生成 5 个 5 以内的四则运算题目,保存到Exercises.txt文件,答案保存到Answers.txt文件,且题目无重复,计算过程无负数,除法结果为真分数。
测试答案检查功能:
输入:准备符合格式的Exercises.txt和Answers.txt文件,运行python math.py -e Exercises.txt -a Answers.txt
预期输出:在Grade.txt文件中正确统计答案的对错数量和编号。
测试边界值:
输入:python math.py -n 1 -r 1
预期输出:生成 1 个 1 以内的四则运算题目,题目和答案符合要求。
测试大量题目生成:
输入:python math.py -n 10000 -r 10
预期输出:成功生成 10000 个 10 以内的四则运算题目,无报错。
测试参数缺失:
输入:python math.py -n 5
预期输出:程序报错并给出帮助信息。
测试错误的文件路径:
输入:python math.py -e wrong_path.txt -a Answers.txt
预期输出:提示文件不存在,请检查路径。
测试复杂表达式:
输入:python math.py -n 1 -r 20
预期输出:生成包含多层括号、多个运算符的复杂表达式题目,答案正确。
测试分数运算:
输入:python math.py -n 3 -r 8
预期输出:生成包含分数运算的题目,答案符合分数运算规则。
测试重复题目判断:
多次输入:python math.py -n 10 -r 6
预期输出:每次生成的题目集合无重复题目。
测试运算符数量限制:
输入:python math.py -n 5 -r 12
预期输出:生成的题目中每个题目运算符数量不超过 3 个。
确定程序正确的原因
对每个功能模块进行了单独测试,如generate_number函数生成的数字符合要求,calculate函数的计算结果准确。
通过大量不同情况的测试用例覆盖,包括边界值、复杂表达式、大量数据等,程序都能按照预期运行,未出现错误结果。
人工核对了部分题目的答案和计算过程,确保程序的计算逻辑正确。
六、PSP 表格(实际时间)
任务 实际耗时(分钟)
需求分析 70
设计程序结构 130
生成数字函数编写 100
生成表达式函数编写 200
计算函数编写 130
检查答案函数编写 130
命令行参数处理 100
测试 150
文档撰写 150
总计 1160
七、项目小结
成败得失
成功之处:完成了程序的基本功能,能够按照要求生成四则运算题目、计算答案以及检查答案的对错。通过合理的函数设计和代码组织,使程序结构较为清晰。
不足之处:在性能优化方面还有提升空间,虽然进行了一些优化,但对于大规模题目生成时,程序运行速度仍有待提高。在测试过程中,发现一些边界情况和特殊情况考虑不够周全,导致出现一些小错误。
经验分享
在项目开始前,充分的需求分析和设计规划非常重要,可以避免后期大量的代码修改。
结对编程能够提高效率,两个人可以相互检查代码,发现对方可能忽略的问题,同时交流不同的思路和方法。
教训总结
对于复杂的需求,应该更加细致地进行拆分和分析,确保每个细节都考虑到。
性能优化要尽早进行,不能等到功能实现后才开始关注,否则可能需要对代码进行较大规模的重构。
结对感受
结对编程过程中,我们相互学习,共同解决遇到的问题,提高了工作效率。在遇到难题时,两人的头脑风暴能够更快地找到解决方案。
闪光点与建议
闪光点:彭鑫对算法和逻辑的理解比较深入,在设计函数和处理复杂逻辑时发挥了重要作用;李锦对代码的细节把控较好,在代码调试和优化方面贡献较大。
建议:在沟通方面可以更加及时和充分,避免因为理解偏差导致的重复工作。同时,可以进一步学习一些高级的编程技巧和工具,提升程序的性能和质量。

posted @ 2025-03-21 22:11  雪牟  阅读(34)  评论(0)    收藏  举报