软件工程结对项目-小学四则运算题目生成与判题程序
软件工程结对项目
一、项目参与成员
计算机科学与技术3班 许晓喆 3223004302
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477 |
这个作业目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
· Estimate | ・估计这个任务需要多少时间 | 30 | 25 |
Development | 开发 | 480 | 540 |
· Analysis | ・需求分析 (包括学习新技术) | 60 | 75 |
· Design Spec | ・生成设计文档 | 45 | 60 |
· Design Review | ・设计复审 (和同事审核设计文档) | 30 | 30 |
· Coding Standard | ・代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
· Design | ・具体设计 | 60 | 75 |
· Coding | ・具体编码 | 180 | 210 |
· Code Review | ・代码复审 | 45 | 45 |
· Test | ・测试(自我测试,修改代码,提交修改) | 45 | 30 |
Reporting | 报告 | 120 | 150 |
· Test Report | ・测试报告 | 45 | 60 |
· Size Measurement | ・计算工作量 | 15 | 15 |
· Postmortem & Process Improvement Plan | ・事后总结,并提出过程改进计划 | 60 | 75 |
合计 | 630 | 715 |
二、设计实现
- 数据结构设计
分数类(Fraction):处理真分数的表示、运算(加、减、乘、除)、约分和格式化输出。
属性:integer(带分数的整数部分,默认 0)、numerator(分子)、denominator(分母,默认 1)。
方法:add、subtract、multiply、divide(分数运算)、reduce(约分)、toString(格式化输出,如2’3/8)、compare(比较大小)。
表达式类(Expression):处理表达式生成、计算和去重。
属性:exprStr(表达式字符串)、result(计算结果,Fraction类型)。
方法:generate(生成随机表达式)、calculate(计算结果)、isDuplicate(判断与其他表达式是否重复,考虑+和×的交换律)。 - 模块划分
命令行解析模块:处理-n、-r、-e、-a等参数,控制程序流程。
题目生成模块:调用Expression类生成满足约束的题目,确保不重复。
文件操作模块:读写Exercises.txt、Answers.txt、Grade.txt。
批改模块:对比题目文件和答案文件,统计对错。
三、代码实现(Python 示例)
- 分数类(Fraction.py)
点击查看代码
class Fraction:def __init__(self, numerator=0, denominator=1, integer=0):self.integer = integerself.numerator = numeratorself.denominator = denominatorself._normalize()self.reduce()def _normalize(self):if self.numerator >= self.denominator:self.integer += self.numerator // self.denominatorself.numerator = self.numerator % self.denominatorif self.integer != 0 and self.numerator == 0:self.denominator = 1def reduce(self):def gcd(a, b):while b:a, b = b, a % breturn ag = gcd(self.numerator, self.denominator)if g != 0:self.numerator //= gself.denominator //= gdef add(self, other):new_denominator = self.denominator * other.denominatornew_numerator = self.numerator * other.denominator + other.numerator * self.denominatornew_integer = self.integer + other.integerreturn Fraction(new_numerator, new_denominator, new_integer)def subtract(self, other):new_denominator = self.denominator * other.denominatornew_numerator = self.numerator * other.denominator - other.numerator * self.denominatornew_integer = self.integer - other.integerreturn Fraction(new_numerator, new_denominator, new_integer)def multiply(self, other):new_numerator = (self.integer * self.denominator + self.numerator) * (other.integer * other.denominator + other.numerator)new_denominator = self.denominator * other.denominatorreturn Fraction(new_numerator, new_denominator).reduce()def divide(self, other):if other.numerator == 0:raise ValueError("除数不能为0")new_numerator = (self.integer * self.denominator + self.numerator) * other.denominatornew_denominator = self.denominator * (other.integer * other.denominator + other.numerator)return Fraction(new_numerator, new_denominator).reduce()def toString(self):if self.integer != 0 and self.numerator != 0:return f"{self.integer}’{self.numerator}/{self.denominator}"elif self.integer != 0:return f"{self.integer}"elif self.numerator != 0:return f"{self.numerator}/{self.denominator}"else:return "0"@staticmethoddef fromString(s):if "'" in s:integer_part, frac_part = s.split("’")numerator, denominator = frac_part.split("/")return Fraction(int(numerator), int(denominator), int(integer_part))elif "/" in s:numerator, denominator = s.split("/")return Fraction(int(numerator), int(denominator), 0)else:return Fraction(0, 1, int(s))def __eq__(self, other):self._normalize()other._normalize()return (self.integer == other.integer andself.numerator == other.numerator andself.denominator == other.denominator)def compare(self, other):self_val = self.integer * self.denominator + self.numeratorother_val = other.integer * other.denominator + other.numeratorself_total = self_val * other.denominatorother_total = other_val * self.denominatorif self_total > other_total:return 1elif self_total == other_total:return 0else:return -1
- 表达式生成与计算(Expression.py)
点击查看代码
import random
from Fraction import Fractionclass Expression:def __init__(self, range_val):self.range_val = range_valself.expr_str = ""self.result = Fraction()self.operators = []def generate_number(self):is_fraction = random.choice([True, False])if is_fraction:denominator = random.randint(2, self.range_val)numerator = random.randint(1, denominator - 1)is_mixed = random.choice([True, False])if is_mixed:integer = random.randint(1, self.range_val - 1)return Fraction(numerator, denominator, integer)else:return Fraction(numerator, denominator, 0)else:return Fraction(0, 1, random.randint(0, self.range_val - 1))def generate_expression(self, depth=0, max_operators=3):if depth == 0 or random.random() < 0.5 or max_operators == 0:num = self.generate_number()self.expr_str = num.toString()self.result = numreturnop = random.choice(["+", "-", "×", "÷"])self.operators.append(op)left = Expression(self.range_val)left.generate_expression(depth + 1, max_operators - 1)right = Expression(self.range_val)right.generate_expression(depth + 1, max_operators - 1)if op == "-":while left.result.compare(right.result) < 0:right.generate_expression(depth + 1, max_operators - 1)if op == "÷":valid = Falsewhile not valid:right.generate_expression(depth + 1, max_operators - 1)left_val = left.result.integer * left.result.denominator + left.result.numeratorright_val = right.result.integer * right.result.denominator + right.result.numeratorif left_val < right_val and (left_val * right.result.denominator) % (right_val * left.result.denominator) == 0:valid = Trueself.expr_str = f"({left.expr_str} {op} {right.expr_str})"if op == "+":self.result = left.result.add(right.result)elif op == "-":self.result = left.result.subtract(right.result)elif op == "×":self.result = left.result.multiply(right.result)elif op == "÷":self.result = left.result.divide(right.result)if depth == 0:self.expr_str = self.expr_str.strip("()")def is_duplicate(self, other):if len(self.operators) != len(other.operators):return Falsedef normalize(expr, ops):parts = []idx = 0for op in ops:part = expr[:expr.index(op)]parts.append(part)expr = expr[expr.index(op) + 1:]parts.append(expr)i = 0while i < len(ops):if ops[i] in ["+", "×"]:j = i + 1while j < len(ops) and ops[j] in ["+", "×"]:j += 1sub_parts = parts[i:i+2]sub_parts.sort()parts[i:i+2] = sub_partsi = jelse:i += 1return parts, opsself_parts, self_ops = normalize(self.expr_str, self.operators)other_parts, other_ops = normalize(other.expr_str, other.operators)return self_parts == other_parts and self_ops == other_ops
- 主程序(main.py)
点击查看代码
import argparse
import os
from Expression import Expression
from Fraction import Fractiondef generate_exercises(num, range_val):exercises = []answers = []while len(exercises) < num:expr = Expression(range_val)expr.generate_expression()duplicate = Falsefor e in exercises:if expr.is_duplicate(e):duplicate = Truebreakif not duplicate:exercises.append(expr)answers.append(expr.result.toString())with open("Exercises.txt", "w", encoding="utf-8") as f:for i, e in enumerate(exercises, 1):f.write(f"{e.expr_str} =\n")with open("Answers.txt", "w", encoding="utf-8") as f:for a in answers:f.write(f"{a}\n")def parse_expression(expr_str):expr_str = expr_str.replace(" ", "")def tokenize(s):tokens = []i = 0while i < len(s):if s[i] in "()+-×÷":tokens.append(s[i])i += 1elif s[i].isdigit() or s[i] in "’/":j = iwhile j < len(s) and (s[j].isdigit() or s[j] in "’/"):j += 1tokens.append(s[i:j])i = jelse:i += 1return tokenstokens = tokenize(expr_str)def parse(tokens):def parse_primary():token = tokens.pop(0)if token == "(":expr = parse(tokens)if tokens.pop(0) != ")":raise SyntaxError("Mismatched parentheses")return exprelse:return Fraction.fromString(token)def parse_term():left = parse_primary()while tokens and tokens[0] in "×÷":op = tokens.pop(0)right = parse_primary()if op == "×":left = left.multiply(right)else:left = left.divide(right)return leftleft = parse_term()while tokens and tokens[0] in "+-":op = tokens.pop(0)right = parse_term()if op == "+":left = left.add(right)else:left = left.subtract(right)return leftreturn parse(tokens)def grade_exercises(exercise_file, answer_file):with open(exercise_file, "r", encoding="utf-8") as f:exercises = [line.strip().replace(" =", "") 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()]correct = []wrong = []for i in range(min(len(exercises), len(answers))):try:calculated = parse_expression(exercises[i])expected = Fraction.fromString(answers[i])if calculated == expected:correct.append(i + 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")if __name__ == "__main__":parser = argparse.ArgumentParser(description="小学四则运算题目生成程序")parser.add_argument("-n", type=int, help="生成题目的个数")parser.add_argument("-r", type=int, help="数值范围(必须提供)")parser.add_argument("-e", type=str, help="题目文件路径")parser.add_argument("-a", type=str, help="答案文件路径")args = parser.parse_args()if args.e and args.a:grade_exercises(args.e, args.a)elif args.n and args.r:generate_exercises(args.n, args.r)else:parser.error("请提供正确的参数组合(-n和-r,或-e和-a)")
四、测试用例
1.功能测试:基本生成
- 命令:python main.py -n 5 -r 5
- 预期:生成 5 道 5 以内的题目,无负数、除法结果为真分数,运算符≤3 个,无重复。
2.边界测试:r=1 - 命令:python main.py -n 3 -r 1
- 预期:仅能生成自然数 0 的运算(如0 + 0 =、0 × 0 =)。
3.去重测试 - 生成题目后,检查Exercises.txt中无2+3=和3+2=同时出现的情况。
4.批改功能测试 - 手动修改Answers.txt中的答案,运行python main.py -e Exercises.txt -a Answers.txt,检查Grade.txt统计是否正确。
5.大规模测试 - 命令:python main.py -n 10000 -r 20
- 预期:成功生成 1 万道题,无内存溢出,文件写入正常。