软件工程第三次作业——结对项目

这个作业属于哪个课程 班级链接
这个作业要求在哪里 [作业要求]https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470)
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序,并能检验题目答案正确性
Github链接 https://github.com/jslisten/3123004378

一.小组:
张翔 3123004378

二.PSP表格:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 10
Estimate 估计这个任务需要多少时间 5 5
Development 开发 10 20
Analysis 需求分析(包括学习新技术) 45 40
Design Spec 生成设计文档 40 40
Design Review 设计复审 10 10
Coding Standard 代码规范 5 5
Design 具体设计 140 150
Coding 具体编码 35 50
Code Review 代码复审 15 20
Test 测试(自我测试,修改代码,提交修改) 30 40
Reporting 报告 15 10
Test Report 测试报告 15 20
Size Measurement 计算工作量 5 5
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 20 30
Total 合计 400 455

三.效能分析
改进思路:
1.优化耗时
总优化耗时约70分钟,主要集中在两个核心问题:题目生成及查重效率低、对答案进行大规模评分。
2.优化思路:
表达式生成算法优化:改进成递归生成
去重机制优化:用哈希集合存储表达式,将复杂度降至o(1)/次
批量生成与并行优化:对大规模的生成任务时,将任务拆成多个子任务并且进行多线程进行运行
内存优化:及时释放冗杂内存,减少内存消耗

四.整体框架设计
image
关键函数流程图:
1.运算题目生成:
开始


解析命令行参数


初始化参数(count, range)


循环生成题目(直到达到count)

├──▼ 随机决定运算符数量(1-3)

├──▼ 生成数字列表(自然数/真分数,范围由range控制)

├──▼ 生成运算符列表(+, -, ×, ÷,记录是否可交换)

├──▼ 递归构建表达式树(组合数字和运算符,支持括号)

├──▼ 验证表达式约束(减法非负、除法结果为真分数)

├──▼ 标准化表达式并检查是否重复

├──▼ 计算答案(调用ExpressionEvaluator)

└──▼ 添加到结果集(Expression对象)


写入文件(Exercises.txt, Answers.txt)


结束
2.计算答案流程:
开始


解析命令行参数(指定题目文件和答案文件)


读取题目文件和答案文件


循环遍历每道题

├──▼ 解析题目,计算正确答案(调用ExpressionEvaluator)

├──▼ 解析用户答案,转换为Fraction对象

└──▼ 对比答案,记录正确/错误题号


统计结果并写入文件(Grade.txt)


结束

模块名称 核心职责 关键方法 / 逻辑
分数处理模块 统一表示自然数、真分数、带分数,实现化简、四则运算(确保减法非负、除法为真分数)及格式转换 simplify ()(化简分数)、四则运算方法(add/__sub__等)、from_string ()(字符串转分数)、str()(分数转字符串)
题目生成模块 生成指定范围、不重复的题目(运算符≤3 个,符合运算规则) generate_number ()(生成随机数字)、_build_expression ()(递归构建表达式)、_normalize_expression ()(标准化去重)、generate_unique_expressions ()(批量生成不重复题目)
表达式计算模块 解析表达式字符串,计算结果(用于生成答案和验证答案) evaluate ()(递归解析并计算表达式)、_find_top_level_operator ()(查找顶层运算符)
评分模块 对比题目与答案文件,统计对错并生成评分结果 读取题目 / 答案文件、调用 ExpressionEvaluator 计算正确答案、对比用户答案(Fraction 对象比较)、写入 Grade.txt

五.核心代码

  1. 分数处理
    class Fraction:
    def init(self, numerator, denominator=1, integer_part=0):
    self.integer_part = integer_part
    self.numerator = numerator
    self.denominator = denominator
    self.simplify() # 初始化即化简

    def simplify(self):
    # 转换带分数(分子≥分母时)
    if self.numerator >= self.denominator:
    self.integer_part += self.numerator // self.denominator
    self.numerator %= self.denominator
    # 分子为0时分母设为1
    if self.numerator == 0:
    self.denominator = 1
    # 约分(GCD)
    if self.numerator != 0 and self.denominator != 1:
    g = self.gcd(abs(self.numerator), abs(self.denominator))
    self.numerator //= g
    self.denominator //= g
    # 确保分母为正
    if self.denominator < 0:
    self.numerator *= -1
    self.denominator *= -1

    @staticmethod
    def gcd(a, b): # 最大公约数计算
    while b:
    a, b = b, a % b
    return a

    四则运算(核心:统一转假分数计算)

    def add(self, other):
    s = self.integer_part * self.denominator + self.numerator
    o = other.integer_part * other.denominator + other.numerator
    return Fraction(s * other.denominator + o * self.denominator, self.denominator * other.denominator)

    def sub(self, other):
    if self < other:
    raise ValueError("减法结果不能为负")
    s = self.integer_part * self.denominator + self.numerator
    o = other.integer_part * other.denominator + other.numerator
    return Fraction(s * other.denominator - o * self.denominator, self.denominator * other.denominator)

    def mul(self, other):
    s = self.integer_part * self.denominator + self.numerator
    o = other.integer_part * other.denominator + other.numerator
    return Fraction(s * o, self.denominator * other.denominator)

    def truediv(self, other):
    s = self.integer_part * self.denominator + self.numerator
    o = other.integer_part * other.denominator + other.numerator
    if o == 0:
    raise ZeroDivisionError
    return Fraction(s * other.denominator, self.denominator * o)

    比较与字符串转换

    def lt(self, other): # 小于比较
    s = self.integer_part * self.denominator + self.numerator
    o = other.integer_part * other.denominator + other.numerator
    return s * other.denominator < o * self.denominator

    def str(self): # 格式化输出(如2'3/8)
    if self.integer_part != 0:
    return f"{self.integer_part}" if self.numerator == 0 else f"{self.integer_part}'{self.numerator}/{self.denominator}"
    return f"{self.numerator}/{self.denominator}" if self.numerator != 0 else "0"

    @classmethod
    def from_string(cls, s): # 从字符串解析(如"2'3/8"→Fraction对象)
    if "'" in s:
    i, f = s.split("'")
    n, d = f.split("/")
    return cls(int(n), int(d), int(i))
    return cls(*map(int, s.split("/"))) if "/" in s else cls(0, 1, int(s))】

2.题目生成
class ExpressionGenerator:
def init(self, max_value, max_operators=3):
self.max_value = max_value # 数值范围
self.max_ops = max_operators # 最大运算符数
self.ops = [('+', operator.add, True), ('-', operator.sub, False),
('×', operator.mul, True), ('÷', operator.truediv, False)] # 运算符(符号/函数/是否可交换)

def generate_number(self):  # 生成范围内的数字(自然数/真分数)
    if random.random() < 0.5:  # 自然数
        return Fraction(0, 1, random.randint(0, self.max_value - 1))
    # 真分数/带分数
    den = random.randint(2, self.max_value)
    num = random.randint(1, den - 1)
    if random.random() < 0.3 and self.max_value > 1:  # 带分数
        return Fraction(num, den, random.randint(1, self.max_value - 1))
    return Fraction(num, den)

def generate_expression(self):  # 生成单题
    num_ops = random.randint(1, self.max_ops)
    components = [self.generate_number()]  # 数字列表
    ops = [random.choice(self.ops) for _ in range(num_ops)]  # 运算符列表
    expr_str, result = self._build(components, ops)
    return Expression(f"{expr_str} =", str(result))

def _build(self, components, ops):  # 递归构建表达式
    if len(components) == 1:
        return str(components[0]), components[0]
    idx = random.randint(0, len(ops) - 1)  # 随机拆分点
    op_sym, op_func, is_comm = ops[idx]
    # 递归构建左右子表达式
    left_str, left_val = self._build(components[:idx+1], ops[:idx])
    right_str, right_val = self._build(components[idx+1:], ops[idx+1:])
    # 处理运算合法性
    try:
        if op_sym == '-':
            if left_val < right_val:  # 减法确保非负
                left_str, right_str, left_val, right_val = right_str, left_str, right_val, left_val
            result = op_func(left_val, right_val)
        elif op_sym == '÷':
            if right_val == Fraction(0, 1, 0):
                raise ZeroDivisionError
            result = op_func(left_val, right_val)
        else:
            result = op_func(left_val, right_val)
    except:  # 非法则重试
        return self.generate_expression().expr_str.replace(" =", ""), self.generate_expression().result
    # 可交换运算符去重(如3+5与5+3统一)
    if is_comm and left_str > right_str:
        left_str, right_str = right_str, left_str
    # 随机加括号
    if len(ops) > 1 and random.random() < 0.5:
        return f"({left_str} {op_sym} {right_str})", result
    return f"{left_str} {op_sym} {right_str}", result

def generate_unique_expressions(self, count):  # 生成不重复题目
    exprs, seen = [], set()
    while len(exprs) < count:
        expr = self.generate_expression()
        norm = self._normalize(expr.expr_str)  # 标准化去重
        if norm not in seen:
            seen.add(norm)
            exprs.append(expr)
    return exprs

def _normalize(self, expr_str):  # 表达式标准化(去重核心)
    expr_str = expr_str.replace(" =", "").strip()
    if expr_str.startswith('(') and expr_str.endswith(')'):
        expr_str = expr_str[1:-1].strip()
    op_idx = self._find_top_op(expr_str)
    if op_idx == -1:
        return expr_str
    op_sym = expr_str[op_idx]
    left, right = expr_str[:op_idx].strip(), expr_str[op_idx+1:].strip()
    left_norm = self._normalize(left)
    right_norm = self._normalize(right)
    # 可交换运算符强制左≤右
    for sym, _, is_comm in self.ops:
        if sym == op_sym and is_comm and left_norm > right_norm:
            left_norm, right_norm = right_norm, left_norm
            break
    return f"{left_norm} {op_sym} {right_norm}"

def _find_top_op(self, expr_str):  # 找顶层运算符(不在括号内)
    paren = 0
    for i, c in enumerate(expr_str):
        if c == '(':
            paren += 1
        elif c == ')':
            paren -= 1
        elif paren == 0:
            for op, _, _ in self.ops:
                if c == op and expr_str[i-1] == ' ' and expr_str[i+1] == ' ':
                    return i
    return -1

3.表达式计算:
class ExpressionEvaluator:
@staticmethod
def evaluate(expr_str): # 解析并计算表达式
expr_str = expr_str.replace("=", "").strip()
if expr_str.startswith('(') and expr_str.endswith(')'):
expr_str = expr_str[1:-1].strip()
op_idx = ExpressionEvaluator._find_top_op(expr_str)
if op_idx == -1: # 单个数
return Fraction.from_string(expr_str)
op_sym = expr_str[op_idx]
left, right = expr_str[:op_idx].strip(), expr_str[op_idx+1:].strip()
left_val = ExpressionEvaluator.evaluate(left)
right_val = ExpressionEvaluator.evaluate(right)
# 执行运算
if op_sym == '+':
return left_val + right_val
elif op_sym == '-':
return left_val - right_val
elif op_sym == '×':
return left_val * right_val
elif op_sym == '÷':
return left_val / right_val

@staticmethod
def _find_top_op(expr_str):  # 同ExpressionGenerator的顶层运算符查找逻辑
    paren = 0
    ops = ['+', '-', '×', '÷']
    for i, c in enumerate(expr_str):
        if c == '(':
            paren += 1
        elif c == ')':
            paren -= 1
        elif paren == 0 and c in ops and expr_str[i-1] == ' ' and expr_str[i+1] == ' ':
            return i
    return -1

4.评分功能:
class ExpressionEvaluator:
@staticmethod
def evaluate(expr_str): # 解析并计算表达式
expr_str = expr_str.replace("=", "").strip()
if expr_str.startswith('(') and expr_str.endswith(')'):
expr_str = expr_str[1:-1].strip()
op_idx = ExpressionEvaluator._find_top_op(expr_str)
if op_idx == -1: # 单个数
return Fraction.from_string(expr_str)
op_sym = expr_str[op_idx]
left, right = expr_str[:op_idx].strip(), expr_str[op_idx+1:].strip()
left_val = ExpressionEvaluator.evaluate(left)
right_val = ExpressionEvaluator.evaluate(right)
# 执行运算
if op_sym == '+':
return left_val + right_val
elif op_sym == '-':
return left_val - right_val
elif op_sym == '×':
return left_val * right_val
elif op_sym == '÷':
return left_val / right_val

@staticmethod
def _find_top_op(expr_str):  # 同ExpressionGenerator的顶层运算符查找逻辑
    paren = 0
    ops = ['+', '-', '×', '÷']
    for i, c in enumerate(expr_str):
        if c == '(':
            paren += 1
        elif c == ')':
            paren -= 1
        elif paren == 0 and c in ops and expr_str[i-1] == ' ' and expr_str[i+1] == ' ':
            return i
    return -1

主函数
if name == "main":
"""主函数"""
try:
opts, args = getopt.getopt(sys.argv[1:], "hn:r:e🅰️")
except getopt.GetoptError:
print("Usage:")
print("Generate exercises: python math_exercises.py -n -r ")
print("Grade exercises: python math_exercises.py -e -a ")
sys.exit(2)

num_exercises = None
range_value = None
exercise_file = None
answer_file = None

for opt, arg in opts:
    if opt == '-h':
        print("Usage:")
        print("Generate exercises: python math_exercises.py -n <number> -r <range>")
        print("Grade exercises: python math_exercises.py -e <exercisefile> -a <answerfile>")
        sys.exit()
    elif opt == '-n':
        num_exercises = int(arg)
    elif opt == '-r':
        range_value = int(arg)
    elif opt == '-e':
        exercise_file = arg
    elif opt == '-a':
        answer_file = arg

# 判断是生成题目还是评分
if exercise_file and answer_file:
    # 评分模式
    grade_exercises(exercise_file, answer_file, "Grade.txt")
elif num_exercises is not None and range_value is not None:
    # 生成题目模式
    if range_value < 1:
        print("Error: Range must be a positive integer")
        sys.exit(2)
    
    generator = ExpressionGenerator(range_value)
    expressions = generator.generate_unique_expressions(num_exercises)
    
    # 写入题目
    with open("Exercises.txt", 'w', encoding='utf-8') as f:
        for expr in expressions:
            f.write(f"{expr}\n")
    
    # 写入答案
    with open("Answers.txt", 'w', encoding='utf-8') as f:
        for expr in expressions:
            f.write(f"{expr.result}\n")
else:
    print("Error: Missing parameters")
    print("Usage:")
    print("Generate exercises: python math_exercises.py -n <number> -r <range>")
    print("Grade exercises: python math_exercises.py -e <exercisefile> -a <answerfile>")
    sys.exit(2)

六.测试运行
输入命令:python math_exercises.py -n 100 -r 20
在answer.txt中:
image

在Excerse.txt中:
image

当输入python math_exercises.py -n 10 -r 5
时输入python math_exercises.py -e Exercises.txt -a Answers.txt
产生的Grade.txt中
image

经检验所有答案都是正确的
当将1,3,8答案改为错误答案时,
image

七.项目小结
1.不足之处:
算法复杂度 :表达式规范化算法复杂度较高,可进一步优化
用户界面 :命令行界面不够友好,可考虑添加图形界面
内存占用:单生成大量题目时内存占用过大

2.成功之处:
有效处理了出现负数及出现除数为0的情况
利用哈希算法可以快速生成大量的题目
提供算法有效地规避了重复题目出现的可能性

3.感受:
通过结对编程,让我深刻体会到了团队的重要性,明白光是一个人的努力有时会导致效率低下,无法有效的进行项目的开发和后续的测试

八.总结:
通过此项目,深刻体会到良好的架构设计比急于编码更重要,充分的测试是质量的保障,同时在进行项目的开发时需要先对所要求的目标进行计划才能够进行有效的实施。

posted @ 2025-10-21 23:50  jslisten  阅读(11)  评论(0)    收藏  举报