结对项目
小学四则运算题目生成程序设计方案
项目 | 内容 |
---|---|
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/ |
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479 |
这个作业的目标 | 实现一个小自动生成小学四则运算题目的程序 |
github仓库链接 | ZzzzHzH/Paired-project |
成员1:张滨皓 | 3123004504 |
成员2:柯程炜 | 3123004486 |
一、项目概述
本项目旨在实现一个自动生成小学四则运算题目的命令行程序,支持生成指定数量和数值范围的题目,同时能够对题目和答案进行判分。
二、需求分析
- 支持生成自然数和真分数的四则运算(+、-、×、÷)题目
- 支持括号运算
- 通过 - n 参数控制题目数量,-r 参数控制数值范围(必须提供)
- 生成的题目需满足:
- 减法运算结果不能为负数
- 除法运算结果应为真分数
- 每道题运算符不超过 3 个
- 题目不能重复(考虑交换律等等价变换)
- 题目和答案分别存入 Exercises.txt 和 Answers.txt
- 支持通过 - e 和 - a 参数对题目和答案进行判分,结果存入 Grade.txt
三、程序设计
3.1 数据结构设计
-
分数类(Fraction):
- 处理分数的表示、运算和格式化输出
- 属性:分子、分母、整数部分(用于带分数)
- 方法:加、减、乘、除、化简、比较大小、转换为字符串
-
表达式类(Expression):
- 表示一个算术表达式
- 属性:表达式字符串、运算符列表、操作数列表、结果
- 方法:生成表达式、计算结果、标准化(用于去重)、转换为字符串
3.2 核心模块设计
- 参数解析模块:解析命令行参数,判断程序运行模式
- 题目生成模块:生成符合要求的四则运算题目
- 表达式计算模块:计算表达式的结果
- 去重模块:确保生成的题目不重复
- 文件操作模块:读写题目、答案和评分结果
- 判分模块:比对题目和答案,生成评分结果
3.3 程序流程图
开始
|
|-- 解析命令行参数
| |
| |-- 生成题目模式(-n和-r)
| | |
| | |-- 验证参数有效性
| | |-- 生成指定数量的题目
| | | |-- 生成随机表达式
| | | |-- 检查是否符合规则(无负数、除法有效等)
| | | |-- 检查是否重复
| | | |-- 计算答案
| | | |-- 添加到题目列表
| | |-- 将题目写入Exercises.txt
| | |-- 将答案写入Answers.txt
| |
| |-- 判分模式(-e和-a)
| |
| |-- 读取题目文件和答案文件
| |-- 对每个题目计算答案
| |-- 与提供的答案比较
| |-- 统计正确和错误的题目
| |-- 将结果写入Grade.txt
|
结束
四、代码实现
4.1 分数类实现
import math
class Fraction:
def __init__(self, numerator=0, denominator=1, integer_part=0):
"""初始化分数,支持整数、纯分数和带分数"""
# 确保分母为正数
if denominator < 0:
numerator = -numerator
denominator = -denominator
# 处理整数部分
if integer_part != 0:
numerator += abs(integer_part) * denominator
if integer_part < 0:
numerator = -numerator
integer_part = 0
self.numerator = numerator
self.denominator = denominator
self.integer_part = integer_part
self.simplify()
def simplify(self):
"""化简分数"""
if self.denominator == 0:
raise ValueError("分母不能为零")
# 处理分子为零的情况
if self.numerator == 0:
self.integer_part = 0
self.denominator = 1
return
# 计算最大公约数
gcd = math.gcd(abs(self.numerator), self.denominator)
self.numerator //= gcd
self.denominator //= gcd
# 分离整数部分
if abs(self.numerator) >= self.denominator:
self.integer_part = self.numerator // self.denominator
self.numerator = self.numerator % self.denominator
# 确保分子为正数
if self.numerator < 0:
self.numerator = -self.numerator
self.integer_part += 1 if self.integer_part < 0 else -1
def __add__(self, other):
"""加法运算"""
new_num = self.to_improper().numerator * other.denominator + other.to_improper().numerator * self.denominator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __sub__(self, other):
"""减法运算"""
new_num = self.to_improper().numerator * other.denominator - other.to_improper().numerator * self.denominator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __mul__(self, other):
"""乘法运算"""
new_num = self.to_improper().numerator * other.to_improper().numerator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __truediv__(self, other):
"""除法运算"""
new_num = self.to_improper().numerator * other.denominator
new_den = self.denominator * other.to_improper().numerator
return Fraction(new_num, new_den)
def __ge__(self, other):
"""大于等于比较"""
return self.to_improper().numerator * other.denominator >= other.to_improper().numerator * self.denominator
def to_improper(self):
"""转换为假分数"""
if self.integer_part == 0:
return Fraction(self.numerator, self.denominator)
return Fraction(self.numerator + abs(self.integer_part) * self.denominator,
self.denominator,
1 if self.integer_part > 0 else -1)
def __str__(self):
"""转换为字符串表示"""
if self.numerator == 0:
return f"{self.integer_part}"
if self.integer_part != 0:
return f"{self.integer_part}'{abs(self.numerator)}/{self.denominator}"
else:
return f"{self.numerator}/{self.denominator}"
def __eq__(self, other):
"""判断两个分数是否相等"""
if not isinstance(other, Fraction):
return False
self_imp = self.to_improper()
other_imp = other.to_improper()
return self_imp.numerator * other_imp.denominator == other_imp.numerator * self_imp.denominator
4.2 表达式生成与计算
import random
import re
class ExpressionGenerator:
def __init__(self, range_limit):
self.range_limit = range_limit
self.operators = ['+', '-', '×', '÷']
self.generated_expressions = set() # 用于存储已生成的表达式的标准化形式,防止重复
def generate_number(self):
"""生成一个随机数(自然数或真分数)"""
is_fraction = random.choice([True, False])
if not is_fraction:
# 生成自然数
return Fraction(integer_part=random.randint(0, self.range_limit - 1))
else:
# 生成真分数
denominator = random.randint(2, self.range_limit - 1)
numerator = random.randint(1, denominator - 1)
# 有一定概率生成带分数
if random.random() < 0.3 and self.range_limit > 1:
integer_part = random.randint(1, self.range_limit - 1)
return Fraction(numerator, denominator, integer_part)
else:
return Fraction(numerator, denominator)
def generate_expression(self, max_ops=3):
"""生成一个表达式"""
if max_ops == 0 or random.random() < 0.3: # 30%的概率生成一个原子表达式
return str(self.generate_number())
# 否则生成一个复合表达式
op_count = random.randint(1, max_ops)
op = random.choice(self.operators)
# 递归生成左右子表达式
left_max_ops = random.randint(0, op_count - 1)
right_max_ops = op_count - 1 - left_max_ops
left_expr = self.generate_expression(left_max_ops)
right_expr = self.generate_expression(right_max_ops)
# 随机添加括号
if random.random() < 0.2:
left_expr = f"({left_expr})"
if random.random() < 0.2:
right_expr = f"({right_expr})"
expr_str = f"{left_expr} {op} {right_expr}"
# 检查表达式是否有效(无负数结果,除法有效)
try:
result = self.evaluate_expression(expr_str)
# 检查减法是否产生负数
if '-' in expr_str and not self.check_subtraction_validity(expr_str):
return self.generate_expression(max_ops)
# 检查除法结果是否为真分数
if '÷' in expr_str and not self.check_division_validity(expr_str):
return self.generate_expression(max_ops)
# 检查是否重复
normalized = self.normalize_expression(expr_str)
if normalized in self.generated_expressions:
return self.generate_expression(max_ops)
self.generated_expressions.add(normalized)
return expr_str
except:
return self.generate_expression(max_ops)
def evaluate_expression(self, expr_str):
"""计算表达式的值"""
# 将表达式中的符号替换为Python可识别的符号
python_expr = expr_str.replace('×', '*').replace('÷', '/')
# 解析分数
def parse_fraction(match):
if "'" in match.group():
# 带分数
integer_part, fraction_part = match.group().split("'")
numerator, denominator = fraction_part.split("/")
return f"({integer_part}) + ({numerator})/({denominator})"
elif "/" in match.group():
# 纯分数
numerator, denominator = match.group().split("/")
return f"({numerator})/({denominator})"
else:
# 整数
return match.group()
# 将表达式中的分数转换为Python表达式
python_expr = re.sub(r"\d+'\d+/\d+|\d+/\d+|\d+", parse_fraction, python_expr)
# 计算结果
result = eval(python_expr)
# 将结果转换为Fraction对象
if isinstance(result, float):
# 处理浮点数,转换为分数
numerator = result * 10**10
denominator = 10**10
return Fraction(int(numerator), int(denominator))
else:
return Fraction(integer_part=int(result))
def check_subtraction_validity(self, expr_str):
"""检查减法是否有效(结果非负)"""
# 这里简化处理,实际应递归检查所有子表达式
# 分割表达式,计算左右两边的值
# 注意:这是一个简化实现,实际需要更复杂的表达式解析
try:
# 找到最外层的减法运算符
# 这只是一个简化的实现
parts = expr_str.split(" - ")
if len(parts) == 2:
left_val = self.evaluate_expression(parts[0])
right_val = self.evaluate_expression(parts[1])
return left_val >= right_val
return True
except:
return False
def check_division_validity(self, expr_str):
"""检查除法是否有效(结果为真分数)"""
# 简化处理,实际应检查所有除法子表达式
try:
result = self.evaluate_expression(expr_str)
# 任何分数都是有效的,因为Fraction类会处理
return True
except:
return False
def normalize_expression(self, expr_str):
"""标准化表达式,用于去重"""
# 处理加法和乘法的交换律
# 这是一个简化实现,实际需要更复杂的处理
if '+' in expr_str and not re.search(r'\([^)]*\+[^)]*\)', expr_str):
parts = expr_str.split(" + ")
if len(parts) == 2:
return " + ".join(sorted(parts))
if '×' in expr_str and not re.search(r'\([^)]*×[^)]*\)', expr_str):
parts = expr_str.split(" × ")
if len(parts) == 2:
return " × ".join(sorted(parts))
return expr_str
4.3 主程序实现
import argparse
import os
def generate_exercises(num, range_limit):
"""生成指定数量的题目"""
generator = ExpressionGenerator(range_limit)
exercises = []
answers = []
for _ in range(num):
expr = generator.generate_expression(3) # 最多3个运算符
exercises.append(f"{expr} =")
answer = generator.evaluate_expression(expr)
answers.append(str(answer))
# 写入文件
with open("Exercises.txt", "w", encoding="utf-8") as f:
for i, exercise in enumerate(exercises, 1):
f.write(f"{i}. {exercise}\n")
with open("Answers.txt", "w", encoding="utf-8") as f:
for i, answer in enumerate(answers, 1):
f.write(f"{i}. {answer}\n")
def grade_exercises(exercise_file, answer_file):
"""对题目和答案进行判分"""
# 读取题目文件
with open(exercise_file, "r", encoding="utf-8") as f:
exercises = [line.strip() for line in f if line.strip()]
# 读取答案文件
with open(answer_file, "r", encoding="utf-8") as f:
answers = [line.strip() for line in f if line.strip()]
if len(exercises) != len(answers):
raise ValueError("题目数量和答案数量不匹配")
correct = []
wrong = []
generator = ExpressionGenerator(100) # 范围不影响判分
for i in range(len(exercises)):
# 提取题目表达式(去除序号和等号)
expr = re.sub(r'^\d+\. ', '', exercises[i]).replace(" =", "")
# 提取答案(去除序号)
user_answer = re.sub(r'^\d+\. ', '', answers[i])
# 计算正确答案
try:
correct_answer = str(generator.evaluate_expression(expr))
# 比较答案
if correct_answer == user_answer:
correct.append(i + 1) # 题目编号从1开始
else:
wrong.append(i + 1)
except:
wrong.append(i + 1)
# 写入评分结果
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:
# 生成题目模式
if args.r < 1:
print("错误:范围参数r必须是大于等于1的自然数")
return
generate_exercises(args.n, args.r)
elif args.e is not None and args.a is not None:
# 判分模式
if not os.path.exists(args.e):
print(f"错误:题目文件{args.e}不存在")
return
if not os.path.exists(args.a):
print(f"错误:答案文件{args.a}不存在")
return
grade_exercises(args.e, args.a)
else:
# 参数错误
print("参数错误,请使用正确的参数组合:")
print("生成题目:Myapp.exe -n 题目数量 -r 范围")
print("判分:Myapp.exe -e 题目文件 -a 答案文件")
parser.print_help()
if __name__ == "__main__":
main()
五、测试用例
-
基本功能测试:
Myapp.exe -n 10 -r 10
验证是否生成 10 道 10 以内的题目和对应的答案。
-
范围测试:
Myapp.exe -n 5 -r 5
验证生成的题目中数值是否都在 5 以内。
-
大量题目测试:
Myapp.exe -n 10000 -r 20
验证程序是否能支持生成 1 万道题目。
-
判分功能测试:
Myapp.exe -e Exercises.txt -a Answers.txt
验证判分功能是否正常,结果是否正确。
-
分数运算测试:
检查生成的分数题目是否正确,如 "1/2 + 1/3 =" 的答案是否为 "5/6"。
-
减法测试:
检查减法题目是否满足被减数大于等于减数,如不会出现 "3 - 5 =" 这样的题目。
-
除法测试:
检查除法题目结果是否为真分数,如 "1 ÷ 2 =" 的答案应为 "1/2"。
-
括号测试:
检查带括号的题目是否正确,如 "(3 + 5) × 2 =" 的答案应为 "16"。
-
重复题目测试:
生成多道题目,检查是否有重复的题目。
-
参数错误测试:
Myapp.exe -n 10 Myapp.exe -r 10 Myapp.exe -e Exercises.txt
验证程序在参数错误时是否能给出正确的提示。
六、PSP 表格
PSP2.1 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|
计划 | 30 | 40 |
・估计任务时间 | 30 | 40 |
开发 | 480 | 540 |
・需求分析 | 60 | 70 |
・生成设计文档 | 40 | 50 |
・设计复审 | 30 | 30 |
・代码规范 | 20 | 20 |
・具体设计 | 60 | 70 |
・具体编码 | 200 | 220 |
・代码复审 | 40 | 50 |
・测试 | 30 | 50 |
报告 | 60 | 70 |
・测试报告 | 20 | 20 |
・计算工作量 | 10 | 10 |
・事后总结 | 30 | 40 |
合计 | 570 | 650 |
七、项目小结
本项目实现了一个功能完整的小学四则运算题目生成程序,支持题目生成和答案判分功能。在实现过程中,主要挑战在于:
- 分数的表示和运算:需要正确处理真分数、带分数的各种运算。
- 表达式的生成和去重:需要确保生成的表达式符合要求,并且不重复。
- 表达式的解析和计算:需要正确解析包含分数和括号的表达式并计算结果。
通过合理的类设计和模块化实现,我们成功解决了这些挑战。程序的扩展性较好,可以方便地添加新的功能,如支持更多类型的题目或更复杂的运算。
在结对开发过程中,团队成员之间的沟通和协作非常重要。通过分工合作,我们能够高效地完成各个模块的开发,并通过代码复审发现和解决潜在的问题。同时,通过互相学习和讨论,我们也提升了自身的编程能力和问题解决能力。
未来可以考虑添加更多功能,如生成不同难度级别的题目、支持小数运算、添加图形界面等,使程序更加完善和易用。