结对项目
| 所属课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
|---|---|
| 作业要求 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230 |
| 作业目标 | 按照要求合作实现一个自动生成小学四则运算题目的命令行程序 |
| 成员1 | 于海洋3122004758 |
| 成员2 | 钟启腾3122004761 |
Github链接: https://github.com/hhai12345678/3122004758
1. PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 40 |
| · Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
| Development | 开发 | 300 | 350 |
| · Analysis | · 需求分析 (包括学习新技术) | 60 | 80 |
| · Design Spec | · 生成设计文档 | 50 | 50 |
| · Design Review | · 设计复审 | 20 | 30 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
| · Design | · 具体设计 | 60 | 80 |
| · Coding | · 具体编码 | 100 | 120 |
| · Code Review | · 代码复审 | 30 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 60 | 80 |
| Reporting | 报告 | 60 | 60 |
| · Test Repor | · 测试报告 | 30 | 40 |
| · Size Measurement | · 计算工作量 | 20 | 20 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
| · 合计 | 890 | 1050 |
2. 效能分析

3. 设计实现过程
3.1 要求分析
-
输入参数:程序需要支持命令行参数 -n、-r、-e 和 -a。其中 -n 表示生成的题目数量,-r 表示数字范围,-e 和 -a 用于验证答案。
-
题目生成:
- 数值范围受 -r 控制,支持自然数和真分数。
- 题目中不能有负数结果,除法结果必须是分数。
- 运算符最多出现 3 次。
-
答案验证:
- 通过对比生成的答案文件与用户提供的答案文件,输出正确与错误的题目编号。
3. 2 实现步骤
-
处理命令行参数。
-
题目生成:
-
随机生成操作数和运算符。
-
检查运算符顺序是否符合规则。
-
检查结果是否合法。
-
-
计算答案。
-
文件输出:
-
题目输出到 Exercises.txt。
-
答案输出到 Answers.txt。
-
-
答案验证:
-
读取用户提供的题目和答案文件。
-
逐一对比,生成统计结果 Grade.txt。
-
3.3 使用方法
生成 10 个数值范围为 10 的题目:
python main.py -n 10 -r 10
验证题目和答案文件:
python main.py -e Exercises.txt -a Answers.txt
3.4 关键函数流程图
main.py

generate_expression(max_range, max_operators)

calculate_expression

4. 代码说明
4.1 代码实现
Main.py代码
import argparse
import random
import fractions
# 定义生成题目时使用的四种基本运算符
OPERATORS = ['+', '-', '*', '/']
# 生成一个分数,分母的最大值由max_denominator参数决定
def generate_fraction(max_denominator):
denominator = random.randint(2, max_denominator) # 随机生成分母,避免分母为1
numerator = random.randint(1, denominator - 1) # 随机生成分子,确保分子小于分母
return fractions.Fraction(numerator, denominator) # 返回一个分数对象
# 生成一个操作数,可以是整数或者分数
def generate_operand(max_range):
if random.choice([True, False]): # 随机选择生成整数或分数
return str(random.randint(1, max_range - 1)) # 返回随机整数,避免整数为0
else:
fraction = generate_fraction(max_range) # 生成一个分数
return f"{fraction.numerator}/{fraction.denominator}" # 返回分数形式的字符串
# 生成一个带有随机操作符的算术表达式,表达式中包含多个操作数和运算符
def generate_expression(max_range, max_operators):
num_operators = random.randint(1, max_operators) # 随机生成表达式中运算符的个数
expression = generate_operand(max_range) # 生成第一个操作数
for _ in range(num_operators):
operator = random.choice(OPERATORS) # 随机选择一个运算符
operand = generate_operand(max_range) # 生成下一个操作数
expression += f" {operator} {operand}" # 将运算符和操作数添加到表达式中
return expression # 返回完整的表达式字符串
# 计算一个表达式的结果
def calculate_expression(expression):
try:
# 通过将字符串分割成单词来解析表达式,假设表达式格式正确
tokens = expression.split()
result = fractions.Fraction(tokens[0]) # 将第一个操作数转换为分数类型
# 遍历剩余的运算符和操作数,并按顺序进行计算
for i in range(1, len(tokens), 2):
operator = tokens[i] # 当前的运算符
operand = fractions.Fraction(tokens[i + 1]) # 当前的操作数(以分数形式表示)
# 根据运算符执行相应的操作
if operator == '+':
result += operand
elif operator == '-':
result -= operand
elif operator == '*':
result *= operand
elif operator == '/':
result /= operand # 确保除法的结果是分数形式
return result # 返回计算结果,类型为Fraction
except ZeroDivisionError: # 捕捉除以零的情况
return None # 如果发生除以零,返回None表示无效的结果
# 生成指定数量的算术题目,并计算其答案
def generate_exercise(num_exercises, max_range, max_operators):
exercises = [] # 存储生成的算术题目
answers = [] # 存储对应的答案
while len(exercises) < num_exercises: # 循环直到生成所需数量的题目
expression = generate_expression(max_range, max_operators) # 生成一个题目
result = calculate_expression(expression) # 计算其答案
if result is not None: # 确保没有除以零的无效结果
exercises.append(expression) # 将题目添加到列表
# 将答案转换为字符串形式,如果是整数则去掉分母
answers.append(
f"{result.numerator}/{result.denominator}" if result.denominator != 1 else str(result.numerator))
return exercises, answers # 返回题目和答案的列表
# 将题目或答案写入文件,每行一个
def write_to_file(filename, lines):
with open(filename, 'w', encoding='utf-8') as file: # 以写模式打开文件
for line in lines:
file.write(line + '\n') # 每行写入一个字符串,并换行
# 从文件中读取内容,返回每行组成的列表
def read_file(filename):
with open(filename, 'r', encoding='utf-8') as file: # 以读模式打开文件
return [line.strip() for line in file] # 读取所有行,并去掉每行末尾的换行符
# 检查答案的正确性,将正确和错误的题目编号分别存储
def check_answers(exercise_file, answer_file):
exercises = read_file(exercise_file) # 从文件读取题目
answers = read_file(answer_file) # 从文件读取答案
correct_indices = [] # 存储正确答案的题目编号
wrong_indices = [] # 存储错误答案的题目编号
# 遍历每个题目及其对应的用户答案
for index, (exercise, user_answer) in enumerate(zip(exercises, answers), start=1):
correct_answer = calculate_expression(exercise) # 计算题目的正确答案
correct_answer_str = f"{correct_answer.numerator}/{correct_answer.denominator}" if correct_answer.denominator != 1 else str(
correct_answer.numerator) # 将正确答案转换为字符串
if user_answer == correct_answer_str: # 检查用户答案是否正确
correct_indices.append(index) # 如果正确,记录题目编号
else:
wrong_indices.append(index) # 如果错误,记录题目编号
return correct_indices, wrong_indices # 返回正确和错误的题目编号
# 将成绩(正确和错误的题目编号)写入文件
def write_grades(correct, wrong):
with open('Grade.txt', 'w', encoding='utf-8') as file: # 以写模式打开成绩文件
file.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n") # 写入正确的题目数量和编号
file.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n") # 写入错误的题目数量和编号
# 程序主函数,处理命令行参数
def main():
parser = argparse.ArgumentParser(description="Generate and check elementary arithmetic problems.") # 创建参数解析器
parser.add_argument('-n', type=int, help="Number of exercises to generate.") # 生成题目的数量
parser.add_argument('-r', type=int, help="Range of numbers in exercises.") # 操作数的范围
parser.add_argument('-e', type=str, help="Exercise file to check answers.") # 用于检查的题目文件
parser.add_argument('-a', type=str, help="Answer file to check answers.") # 用于检查的答案文件
args = parser.parse_args() # 解析命令行参数
# 如果提供了生成题目所需的参数,生成题目并保存到文件
if args.n and args.r:
exercises, answers = generate_exercise(args.n, args.r, 3) # 生成题目和答案,最大操作符数量为3
write_to_file('Exercises.txt', exercises) # 将题目写入文件
write_to_file('Answers.txt', answers) # 将答案写入文件
print(f"Generated {args.n} exercises with answers.") # 打印生成题目的信息
# 如果提供了检查答案的文件,检查答案并生成成绩
elif args.e and args.a:
correct, wrong = check_answers(args.e, args.a) # 检查答案
write_grades(correct, wrong) # 写入成绩
print("Checked answers and wrote grades to Grade.txt.") # 打印检查完成的信息
else:
parser.print_help() # 如果参数不完整,打印帮助信息
# 运行主函数
if __name__ == "__main__":
main()
测试单元代码
import random
import unittest
import fractions
from io import StringIO
import sys
from main import (generate_fraction, generate_operand, generate_expression,
calculate_expression, generate_exercise, write_to_file, read_file,
check_answers)
class TestMathExercise(unittest.TestCase):
"""
TestMathExercise类用于测试与数学练习生成相关的函数。
"""
def test_generate_fraction(self):
"""
测试生成分数函数 generate_fraction(n) 是否返回合法的 fractions.Fraction 实例。
分子应当大于等于 1,分母应当大于等于 2。
"""
fraction = generate_fraction(10)
self.assertTrue(isinstance(fraction, fractions.Fraction)) # 检查生成结果是否为 Fraction 实例
self.assertGreaterEqual(fraction.denominator, 2) # 检查分母是否符合条件
self.assertGreaterEqual(fraction.numerator, 1) # 检查分子是否符合条件
def test_generate_expression(self):
"""
测试生成表达式函数 generate_expression(max_value, num_operands)。
确保生成的表达式中包含加减乘除运算符。
使用固定随机种子以保证测试结果一致。
"""
random.seed(0) # 固定随机种子以保证测试的可重复性
expression = generate_expression(10, 3)
# 确保表达式中至少包含一个四则运算符号
self.assertTrue(any(op in expression for op in ['+', '-', '*', '/']))
def test_calculate_expression_addition(self):
"""
测试计算表达式的函数 calculate_expression(expression) 对加法表达式的处理。
例如: "1/2 + 1/2" 应该返回 1。
"""
expression = "1/2 + 1/2"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 1)) # 检查结果是否为1
def test_calculate_expression_subtraction(self):
"""
测试 calculate_expression(expression) 对减法表达式的处理。
例如:"3/4 - 1/4" 应该返回 1/2。
"""
expression = "3/4 - 1/4"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 2)) # 检查结果是否为1/2
def test_calculate_expression_multiplication(self):
"""
测试 calculate_expression(expression) 对乘法表达式的处理。
例如:"2/3 * 3/4" 应该返回 1/2。
"""
expression = "2/3 * 3/4"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 2)) # 检查结果是否为1/2
def test_calculate_expression_division(self):
"""
测试 calculate_expression(expression) 对除法表达式的处理。
例如:"2/3 / 4/5" 应该返回 10/12 (即 5/6)。
"""
expression = "2/3 / 4/5"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(10, 12)) # 检查结果是否为10/12
def test_generate_exercise(self):
"""
测试生成练习题函数 generate_exercise(num_exercises, max_value, num_operands)。
确保生成的练习题数目与答案数目都符合预期(即 num_exercises 个)。
"""
exercises, answers = generate_exercise(5, 10, 3)
self.assertEqual(len(exercises), 5) # 检查生成的练习题数量是否正确
self.assertEqual(len(answers), 5) # 检查生成的答案数量是否正确
def test_check_answers(self):
"""
测试检查答案的函数 check_answers(exercise_file, answer_file)。
这里通过创建包含四个算式的文件,然后检查答案是否全部正确。
"""
exercises = ["1/2 + 1/2", "3/4 - 1/4", "2/3 * 3/4", "2/3 / 4/5"]
answers = ["1", "1/2", "1/2", "5/6"]
# 模拟写入“练习题”和“答案”文件
with open('Exercises.txt', 'w') as f_ex:
f_ex.write('\n'.join(exercises))
with open('Answers.txt', 'w') as f_ans:
f_ans.write('\n'.join(answers))
# 调用 check_answers 函数检查答案
correct, wrong = check_answers('Exercises.txt', 'Answers.txt')
self.assertEqual(correct, [1, 2, 3, 4]) # 检查是否所有答案都正确
self.assertEqual(wrong, []) # 检查是否没有错误答案
def test_write_to_file(self):
"""
测试写文件函数 write_to_file(filename, data)。
确保文件中写入的数据与原始数据相同。
"""
data = ["1 + 1", "2 - 2", "3 * 3", "4 / 4"]
write_to_file("TestOutput.txt", data)
# 读取文件并验证内容是否正确
read_back = read_file("TestOutput.txt")
self.assertEqual(data, read_back) # 检查文件内容是否与原始数据一致
def test_read_file(self):
"""
测试读文件函数 read_file(filename)。
确保从文件中读取的数据与原始写入的数据相同。
"""
data = ["5 + 5", "6 - 6"]
write_to_file("TestRead.txt", data)
read_back = read_file("TestRead.txt")
self.assertEqual(data, read_back) # 检查读取的数据是否正确
if __name__ == '__main__':
unittest.main()
4.2 详细说明
-
generate_fraction: 生成随机真分数。
-
generate_operand: 生成随机操作数,可以是自然数或真分数。
-
generate_expression: 根据给定的操作符数量生成一个表达式。
-
calculate_expression: 计算生成的表达式的结果,如果有除以零等非法操作,返回 None。
-
generate_exercise: 根据给定数量和范围生成不重复的题目和答案。
-
write_to_file: 将生成的题目或答案写入文件。
-
read_file: 从文件读取题目或答案。
-
check_answers: 对比题目文件和答案文件,输出正确和错误的编号。
-
write_grades: 将对错题目编号写入文件 Grade.txt。
5. 测试运行

6. 实际运行结果




7. 项目小结
-
在本次项目中,我们加深了对 Python语言理解和掌握。
-
通过设计和实现一个具有特定功能的程序——从需求分析到设计,再到代码实现和测试,我们经历了完整的软件开发流程,积累了宝贵的实践经验。
-
我们学会了共同沟通,共同思考得出解决方法,提高了通过团队解决问题的能力。

浙公网安备 33010602011771号