结对项目
| 姓名 | 学号 | github |
|---|---|---|
| 黄旭彪 | 3123003446 | https://github.com/Ryon-h/Elementary-Arithmetic-Exercise-Generater |
| 郑岱扬 | 3123004812 |
psp2.1
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 120 | 140 |
| · Estimate | 估计这个任务需要多少时间 | 120 | 140 |
| Development | 开发 | 300 | 455 |
| · Analysis | 需求分析 (包括学习新技术) | 60 | 60 |
| · Design Spec | 生成设计文档 | 20 | 45 |
| · Design Review | 设计复审 | 30 | 30 |
| · Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | 具体设计 | 30 | 30 |
| · Coding | 具体编码 | 120 | 90 |
| · Code Review | 代码复审 | 30 | 40 |
| · Test | 测试(自我测试,修改代码,提交修改) | 60 | 150 |
| Reporting | 报告 | 80 | 285 |
| · Test Report | 测试报告 | 30 | 120 |
| · Size Measurement | 计算工作量 | 20 | 40 |
| · Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 125 |
| Total | 合计 | 500(8小时) | 880(13小时) |
性能优化过程记录
优化时间分配:
-
代码分析:30分钟
-
性能瓶颈识别:45分钟
-
优化实施:1.5小时
-
测试验证:1小时
总计:3.75小时
优化思路: -
使用性能分析工具(如cProfile)识别热点函数
-
优化数据结构和算法复杂度
-
实现并行处理
-
减少I/O操作频率
-
使用缓存机制
优化建议
- 使用批量处理替代频繁的文件I/O
- 引入多线程处理大量数据
- 优化数据结构,减少内存占用
- 使用缓存机制减少重复计算
- 采用更高效的算法实现
性能分析图表
我创建了性能分析函数profiling,程序会自动执行三组不同规模的测试用例(100题、1000题和10000题),并为每组测试显示性能分析结果。分析结果会显示最耗时的10个函数,包括它们的调用次数、累计时间等信息,这些信息可以帮助我们找出程序中的性能瓶颈。


整体架构设计
项目采用面向对象的设计方法,主要包含以下核心模块:
-
主程序模块 (
main.py)- 负责程序入口
- 处理命令行参数
- 协调其他模块工作
-
算术题目生成器 (
arithmetic_generator.py)- 核心类: ArithmeticGenerator
- 负责生成算术表达式
- 计算表达式结果
-
答案检查器 (
answer_checker.py)- 核心类: AnswerChecker
- 负责验证答案正确性
- 生成检查报告
数据流向
- 命令行参数 → Main程序
- Main程序 → ArithmeticGenerator(生成题目)
- ArithmeticGenerator → 文件系统(保存题目和答案)
- Main程序 → AnswerChecker(检查答案)
- AnswerChecker → 文件系统(读取答案)
- AnswerChecker → 控制台(输出结果)
扩展性考虑
- 题目生成器支持扩展新的运算符
- 答案检查器支持不同格式的答案文件
- 可以轻松添加新的输出格式
- 支持自定义题目难度和规则
错误处理
- 文件操作异常处理
- 输入参数验证
- 计算过程异常处理
- 格式解析错误处理
函数关系图

主程序流程图

题目生成器流程图

答案检查器流程图

代码展示与说明
main函数
`import argparse
from arithmetic_generator import ArithmeticGenerator
# 导入答案检查模块
from answer_checker import AnswerChecker
def parse_arguments():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description='四则运算题目生成程序')
parser.add_argument('-n', type=int, default=10, help='要生成的题目数量')
parser.add_argument('-r', type=int, default=10, help='数值范围')
parser.add_argument('-e', type=str, help='题目文件')
parser.add_argument('-a', type=str, help='答案文件')
return parser.parse_args()
def main():
args = parse_arguments()
# 如果提供了题目文件和答案文件,则进行答案校验
if args.e and args.a:
checker = AnswerChecker()
checker.check_answers(args.e, args.a)
# 否则生成新的题目
else:
generator = ArithmeticGenerator(args.r)
exercises = generator.generate_exercises(args.n)
# 将题目和答案写入文件
with open('Exercises.txt', 'w', encoding='utf-8') as f:
for i, exercise in enumerate(exercises, 1):
f.write(f'{i}. {exercise["question"]}\n')
with open('Answers.txt', 'w', encoding='utf-8') as f:
for i, exercise in enumerate(exercises, 1):
f.write(f'{i}. {exercise["answer"]}\n')
print(f'已生成{args.n}道题目,数值范围为{args.r}')
print('题目已保存到 Exercises.txt')
print('答案已保存到 Answers.txt')
if __name__ == '__main__':
main()`
arithmeticgenerato类
`import argparse
import random
import fractions
from typing import List, Set, Tuple, Union
class ArithmeticGenerator:
def __init__(self, range_limit: int):
self.range_limit = range_limit
self.generated_expressions: Set[str] = set()
def generate_number(self) -> Union[int, str]:
"""生成一个随机数(整数或真分数)"""
# 随机决定生成整数还是分数
if random.random() < 0.7: # 70%概率生成整数
return random.randint(0, self.range_limit - 1)
else:
# 生成真分数
numerator = random.randint(1, self.range_limit - 1)
denominator = random.randint(numerator + 1, self.range_limit)
# 如果分子大于分母,转换为带分数格式
if numerator >= denominator:
whole = numerator // denominator
numerator = numerator % denominator
return f"{whole}'{numerator}/{denominator}" if numerator != 0 else str(whole)
return f"{numerator}/{denominator}"
def evaluate_expression(self, expr: str) -> Union[int, fractions.Fraction]:
"""计算表达式的值"""
def parse_number(num_str: str) -> Union[int, fractions.Fraction]:
if '/' in num_str:
if "'" in num_str:
whole, frac = num_str.split("'")
num, den = map(int, frac.split("/"))
return fractions.Fraction(int(whole) * den + num, den)
else:
num, den = map(int, num_str.split("/"))
return fractions.Fraction(num, den)
return int(num_str)
# 将表达式转换为后缀表达式
def infix_to_postfix(expr: str) -> List[str]:
precedence = {'+': 1, '-': 1, '×': 2, '÷': 2}
stack = []
output = []
tokens = expr.replace('(', ' ( ').replace(')', ' ) ').split()
for token in tokens:
if token in '+-×÷':
while (stack and stack[-1] != '(' and
precedence[stack[-1]] >= precedence[token]):
output.append(stack.pop())
stack.append(token)
elif token == '(':
stack.append(token)
elif token == ')':
while stack and stack[-1] != '(':
output.append(stack.pop())
if stack:
stack.pop()
else:
output.append(token)
while stack:
output.append(stack.pop())
return output
# 计算后缀表达式
def evaluate_postfix(tokens: List[str]) -> Union[int, fractions.Fraction]:
stack = []
for token in tokens:
if token in '+-×÷':
b = stack.pop()
a = stack.pop()
if token == '+':
stack.append(a + b)
elif token == '-':
if b > a: # 检查是否会产生负数
raise ValueError("产生负数")
stack.append(a - b)
elif token == '×':
stack.append(a * b)
elif token == '÷':
if b == 0:
raise ValueError("除数为零")
result = a / b
if result >= 1:
raise ValueError("除法结果不是真分数")
stack.append(result)
else:
stack.append(parse_number(token))
return stack[0]
try:
return evaluate_postfix(infix_to_postfix(expr))
except Exception as e:
raise ValueError(f"表达式计算错误: {str(e)}")
def is_valid_expression(self, expr: str) -> bool:
"""检查表达式是否有效(不产生负数且除法结果为真分数)"""
try:
self.evaluate_expression(expr)
return True
except ValueError:
return False
def generate_expression(self) -> str:
"""生成一个随机算术表达式"""
operators = ['+', '-', '×', '÷']
max_operators = random.randint(1, 3) # 随机决定使用1-3个运算符
# 生成第一个数
expr = str(self.generate_number())
# 添加运算符和数字
for _ in range(max_operators):
op = random.choice(operators)
num = str(self.generate_number())
# 随机决定是否添加括号
if random.random() < 0.3 and op in '×÷': # 30%概率添加括号
expr = f"({expr})"
expr = f"{expr} {op} {num}"
# 验证当前表达式是否有效
if not self.is_valid_expression(expr):
# 如果无效,回退到上一个有效状态
return self.generate_expression()
return f"{expr} ="
def generate_exercises(self, count: int) -> List[dict]:
"""生成指定数量的习题"""
exercises = []
while len(exercises) < count:
expr = self.generate_expression()
if expr not in self.generated_expressions:
try:
# 去掉表达式末尾的等号
question = expr
answer = str(self.evaluate_expression(expr.rstrip(' =')))
exercises.append({"question": question, "answer": answer})
self.generated_expressions.add(expr)
except ValueError:
continue
return exercises
def main():
parser = argparse.ArgumentParser(description='四则运算题目生成器')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-n', type=int, help='生成题目的数量')
group.add_argument('-e', type=str, help='练习题文件路径')
parser.add_argument('-r', type=int, help='数值范围(必需)')
parser.add_argument('-a', type=str, help='答案文件路径')
args = parser.parse_args()
if args.n:
if not args.r:
parser.error('使用-n参数时必须指定-r参数')
if args.r < 2:
parser.error('-r参数必须大于1')
generator = ArithmeticGenerator(args.r)
exercises = generator.generate_exercises(args.n)
# 保存习题到文件
with open('Exercises.txt', 'w', encoding='utf-8') as f:
for i, exercise in enumerate(exercises, 1):
f.write(f'{exercise}\n')
# TODO: 计算答案并保存到文件
elif args.e and args.a:
# TODO: 实现答案验证功能
pass
if __name__ == '__main__':
main()`
anwer_checker
`class AnswerChecker:
def check_answers(self, exercise_file, answer_file):
"""检查答案文件中的答案是否正确,并将结果输出到Grade.txt文件中
Args:
exercise_file (str): 题目文件路径
answer_file (str): 答案文件路径
"""
# 读取题目文件
with open(exercise_file, 'r', encoding='utf-8') as f:
exercises = f.readlines()
# 读取答案文件
with open(answer_file, 'r', encoding='utf-8') as f:
answers = f.readlines()
# 检查答案数量是否匹配
if len(exercises) != len(answers):
print('错误:题目数量与答案数量不匹配')
return
correct_indices = []
wrong_indices = []
total_count = len(exercises)
# 逐题检查答案
for i, (exercise, answer) in enumerate(zip(exercises, answers)):
# 获取题号
question_num = i + 1
# 去除题号和空白字符
exercise = exercise.split('.', 1)[1].strip()
answer = answer.split('.', 1)[1].strip()
# 计算正确答案
try:
correct_answer = str(eval(exercise))
if correct_answer == answer:
correct_indices.append(question_num)
else:
wrong_indices.append(question_num)
except:
print(f'警告:第{question_num}题无法计算')
wrong_indices.append(question_num)
# 将结果写入Grade.txt文件
with open('Grade.txt', 'w', encoding='utf-8') as f:
f.write(f'Correct: {len(correct_indices)} {str(tuple(correct_indices)).replace(" ", "")}\n')
f.write(f'Wrong: {len(wrong_indices)} {str(tuple(wrong_indices)).replace(" ", "")}')
# 输出统计结果
print(f'结果已写入Grade.txt文件')`
这是一个四则运算题目生成器的Python程序,主要包含以下功能:
- ArithmeticGenerator类:负责生成算术表达式,包括生成随机数(整数或分数)、验证表达式有效性、计算表达式结果等核心功能。其中generate_number方法生成随机数,evaluate_expression方法计算表达式值,is_valid_expression方法验证表达式合法性,generate_expression方法生成完整算术表达式,generate_exercises方法批量生成习题。
- main函数:程序的入口点,处理命令行参数并调用相应功能。目前实现了通过-n参数生成指定数量的习题,并将习题保存到文件的功能。程序支持的参数包括:-n(生成题目数量)、-e(练习题文件路径)、-r(数值范围)、-a(答案文件路径)。
- 表达式处理功能:程序使用中缀表达式转后缀表达式的方法来计算表达式结果,支持括号、四则运算,并能正确处理分数运算,同时确保不会产生负数和除法结果为真分数。
测试用例
随机生成random每次用例不同,为了实现grade程序,运行run脚本含有三个用例,分别为
f.write('1. 1 + 2\n')
f.write('2. 3 * 4\n')
f.write('3. 10 - 5\n')
四则运算生成器项目总结
作者:黄旭彪 & 郑岱扬
日期:2025-03-24
项目概述
我们合作开发了一个四则运算题目生成器,这是我们第一次尝试结对编程。项目实现了题目生成、答案验证等核心功能,并且支持命令行操作。
技术实现亮点
-
模块化设计
- 清晰的职责划分
- 良好的代码组织
- 高内聚低耦合
-
可扩展性
- 支持自定义题目数量
- 灵活的数值范围设置
- 预留了扩展接口
成功经验
合理的分工
- hxb负责核心算法实现
- zdy负责测试用例编写
- 共同完成代码审查
遇到的挑战
1. 技术难点
"最初在处理分数运算时遇到了一些困难,但通过结对编程,我们一起找到了更优的解决方案。"
2. 时间管理
"有时候因为工作时间安排不一致,导致协作效率受影响。后来我们固定了每天的结对时间,情况就好多了。"
改进建议
-
开发流程优化
- 引入自动化测试
- 完善文档管理
- 建立代码规范
-
团队协作提升
- 更频繁的代码审查
- 建立知识共享机制
- 优化任务分配方式
结对感受
感受
"结对编程让我学会了如何更好地表达自己的想法,同时也从搭档那里学到了很多新的编程技巧。这种合作模式确实能够提高代码质量,减少bug的产生。"
"通过结对编程,我不仅提高了编程能力,还学会了如何更好地与他人合作。实时的代码审查和讨论让我对代码质量有了更深的认识。"
未来展望
-
功能扩展
- 支持更多运算类型
- 添加难度等级
- 优化用户界面
-
技术提升
- 引入新的设计模式
- 优化算法效率
- 加强代码健壮性
总结
结对编程不仅仅是一种开发模式,更是一种促进团队成长的有效方式。通过这次项目,我们都收获了宝贵的经验,也建立了更好的合作关系。期待在未来的项目中能够继续保持这种高效的协作模式。
浙公网安备 33010602011771号