软件工程结对项目-小学四则运算题目生成与判题程序
软件工程结对项目
一、项目参与成员
计算机科学与技术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 = integer
self.numerator = numerator
self.denominator = denominator
self._normalize()
self.reduce()
def _normalize(self):
if self.numerator >= self.denominator:
self.integer += self.numerator // self.denominator
self.numerator = self.numerator % self.denominator
if self.integer != 0 and self.numerator == 0:
self.denominator = 1
def reduce(self):
def gcd(a, b):
while b:
a, b = b, a % b
return a
g = gcd(self.numerator, self.denominator)
if g != 0:
self.numerator //= g
self.denominator //= g
def add(self, other):
new_denominator = self.denominator * other.denominator
new_numerator = self.numerator * other.denominator + other.numerator * self.denominator
new_integer = self.integer + other.integer
return Fraction(new_numerator, new_denominator, new_integer)
def subtract(self, other):
new_denominator = self.denominator * other.denominator
new_numerator = self.numerator * other.denominator - other.numerator * self.denominator
new_integer = self.integer - other.integer
return 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.denominator
return 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.denominator
new_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"
@staticmethod
def 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 and
self.numerator == other.numerator and
self.denominator == other.denominator)
def compare(self, other):
self_val = self.integer * self.denominator + self.numerator
other_val = other.integer * other.denominator + other.numerator
self_total = self_val * other.denominator
other_total = other_val * self.denominator
if self_total > other_total:
return 1
elif self_total == other_total:
return 0
else:
return -1
- 表达式生成与计算(Expression.py)
点击查看代码
import random
from Fraction import Fraction
class Expression:
def __init__(self, range_val):
self.range_val = range_val
self.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 = num
return
op = 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 = False
while not valid:
right.generate_expression(depth + 1, max_operators - 1)
left_val = left.result.integer * left.result.denominator + left.result.numerator
right_val = right.result.integer * right.result.denominator + right.result.numerator
if left_val < right_val and (left_val * right.result.denominator) % (right_val * left.result.denominator) == 0:
valid = True
self.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 False
def normalize(expr, ops):
parts = []
idx = 0
for op in ops:
part = expr[:expr.index(op)]
parts.append(part)
expr = expr[expr.index(op) + 1:]
parts.append(expr)
i = 0
while i < len(ops):
if ops[i] in ["+", "×"]:
j = i + 1
while j < len(ops) and ops[j] in ["+", "×"]:
j += 1
sub_parts = parts[i:i+2]
sub_parts.sort()
parts[i:i+2] = sub_parts
i = j
else:
i += 1
return parts, ops
self_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 Fraction
def generate_exercises(num, range_val):
exercises = []
answers = []
while len(exercises) < num:
expr = Expression(range_val)
expr.generate_expression()
duplicate = False
for e in exercises:
if expr.is_duplicate(e):
duplicate = True
break
if 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 = 0
while i < len(s):
if s[i] in "()+-×÷":
tokens.append(s[i])
i += 1
elif s[i].isdigit() or s[i] in "’/":
j = i
while j < len(s) and (s[j].isdigit() or s[j] in "’/"):
j += 1
tokens.append(s[i:j])
i = j
else:
i += 1
return tokens
tokens = 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 expr
else:
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 left
left = 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 left
return 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 万道题,无内存溢出,文件写入正常。