【作业3】结对项目:实现一个自动生成小学四则运算题目的命令行程序

news/2025/3/15 21:46:54/文章来源:https://www.cnblogs.com/zyjue/p/18774241

成员:3223004473詹艺珏  and   3223004301吴梦琪
📎Github链接:https://github.com/Jue610/Jue610/tree/main/ArithProbelm

这个作业属于哪个课程 23软件工程
这个作业要求在哪里 【作业3】结对项目
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序,培养团队协作和沟通交流能力

一、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate 估计这个任务需要多少时间 30 30
Development 开发 440 380
· Analysis 需求分析 (包括学习新技术) 60 30
· Design Spec 生成设计文档 40 30
· Design Review 设计复审 30 10
· Coding Standard 代码规范 (制定开发规范) 10 10
· Design 具体设计 30 40
· Coding 具体编码 180 120
· Code Review 代码复审 30 60
· Test 测试(自我测试,修改代码,提交修改) 60 80
Reporting 报告 130 160
· Test Report 测试报告 90 120
· Size Measurement 计算工作量 10 10
· Postmortem 事后总结, 并提出过程改进计划 30 30
Total 合计 600 570

一、程序功能

  1. 目的
    • 主要用于生成数学表达式的练习题并计算答案,同时也能检查用户对这些练习题答案的正确性。
    • 它可以随机生成包含自然数、分数和带分数的四则运算表达式,将其规范化后写入练习题文件,计算答案并写入答案文件,最后根据用户提供的答案文件检查答案的正确性并生成成绩文件。
  2. 输入输出
  • 输入:通过命令行参数接受输入:
  • 生成练习题的数量-n和表达式中数字的范围-r来生成练习题;
python main.py -n 10000 -r 100
  • 练习题文件-e和答案文件-a来检查答案。
python main.py -e Exercises.txt -a Answers.txt
  • 输出:生成练习题文件Exercises.txt、答案文件Answers.txt以及成绩文件Grade.txt

二、代码结构与模块分析

  1. 类的定义
    • Node
      • 功能:用于构建表达式树。
      • 结构
        • type属性,表示节点类型(如numberoperator)。
        • value属性,当typenumber时存储分数值(以元组形式表示分子分母)。
        • operator属性,当typeoperator时存储操作符(如+-×÷)。
        • leftright属性,用于指向表达式树的左子树和右子树。
class Node:def __init__(self, type, value=None, operator=None, left=None, right=None):self.type = typeself.value = valueself.operator = operatorself.left = leftself.right = right
  1. 函数模块与接口设置
    • simplify_fraction函数
      • 功能:通过计算最大公约数来化简分数。
      • 使用递归的方式计算分子分母的最大公约数,然后将分数化简。
def simplify_fraction(numerator, denominator):gcd = lambda a, b: a if b == 0 else gcd(b, a % b)common = gcd(abs(numerator), abs(denominator))return (numerator // common, denominator // common)
  • generate_number函数
    • 功能:随机生成自然数、分数或带分数。
    • 首先随机选择一种数字类型(自然数、分数、带分数),然后根据类型生成相应的数字,并将其包装成Node类型的对象。
def generate_number(r):choice = random.choices(['natural', 'fraction', 'mixed'], weights=[3, 2, 2], k=1)[0]if choice == 'natural':num = random.randint(0, r - 1)return Node('number', value=simplify_fraction(num, 1))elif choice == 'fraction':denominator = random.randint(2, r)numerator = random.randint(1, denominator - 1)return Node('number', value=simplify_fraction(numerator, denominator))else:integer = random.randint(1, r - 1)denominator = random.randint(2, r)numerator = random.randint(1, denominator - 1)total_numerator = integer * denominator + numeratorreturn Node('number', value=simplify_fraction(total_numerator, denominator))
  • generate_expression函数
    • 功能:递归生成四则运算表达式树。
    • 如果最大操作符数量为0,则生成一个数字节点;否则选择一个操作符,递归生成左右子表达式树,最后构建一个操作符节点并返回。
def generate_expression(r, max_ops):if max_ops == 0:return generate_number(r)else:op = random.choice(['+', '-', '×', '÷'])left_ops = random.randint(0, max_ops - 1)right_ops = max_ops - 1 - left_opsleft = generate_expression(r, left_ops)right = generate_expression(r, right_ops)return Node('operator', operator=op, left=left, right=right)
  • compute_value函数
    • 功能:计算表达式树的值。
    • 如果是数字节点直接返回其值;否则先计算左右子树的值,然后根据操作符进行相应的四则运算,并化简结果。
def compute_value(node):if node.type == 'number':return node.valueleft = compute_value(node.left)right = compute_value(node.right)ln, ld = leftrn, rd = rightif node.operator == '+':numerator = ln * rd + rn * lddenominator = ld * rdelif node.operator == '-':numerator = ln * rd - rn * lddenominator = ld * rdif numerator < 0:raise ValueError("Negative result")elif node.operator == '×':numerator = ln * rndenominator = ld * rdelif node.operator == '÷':if rn == 0:raise ValueError("Division by zero")numerator = ln * rddenominator = ld * rnif numerator / denominator >= 1:raise ValueError("Improper fraction")return simplify_fraction(numerator, denominator)
  • fraction_to_string函数
    • 功能:将分数转换为字符串形式。
    • 根据分子分母的关系判断并构建相应的字符串形式。
def fraction_to_string(numerator, denominator):if denominator == 1:return str(numerator)integer = numerator // denominatorremainder = numerator % denominatorif integer == 0:return f"{remainder}/{denominator}"else:return f"{integer}'{remainder}/{denominator}" if remainder != 0 else str(integer)
  • to_string函数
    • 功能:将表达式树转换为字符串形式。
    • 如果是数字节点,调用fraction_to_string函数转换为字符串;否则根据操作符的优先级和括号的需求,递归地将左右子树转换为字符串并构建表达式字符串。
def to_string(node, parent_priority=0):if node.type == 'number':return fraction_to_string(*node.value)current_priority = 2 if node.operator in ['×', '÷'] else 1left_str = to_string(node.left, current_priority)right_str = to_string(node.right, current_priority)expr_str = f"{left_str} {node.operator} {right_str}"if current_priority < parent_priority:expr_str = f"({expr_str})"return expr_str
  • normalize函数
    • 功能:对表达式树进行规范化处理。
    • 如果是数字节点直接返回;对于加法和乘法操作符,将表达式树展开为项列表,排序后重新构建表达式树;对于减法和除法操作符,递归地规范化左右子树。
def normalize(node):if node.type == 'number':return nodeif node.operator in ['+', '×']:terms = []stack = [node]while stack:current = stack.pop()if current.type == 'operator' and current.operator == node.operator:stack.append(current.right)stack.append(current.left)else:terms.append(current)terms = [normalize(term) for term in terms]terms.sort(key=lambda x: to_string(x))root = terms[0]for term in terms[1:]:root = Node('operator', operator=node.operator, left=root, right=term)return rootelse:return Node('operator', operator=node.operator, left=normalize(node.left), right=normalize(node.right))
  • generate_problems函数
    • 功能:生成指定数量的练习题并计算答案。
    • 通过循环随机生成表达式,规范化后将表达式和答案分别写入练习题文件和答案文件。
def generate_problems(n, r):generated = set()exercises = []answers = []while len(exercises) < n:expr = generate_expression(r, 3)normalized = normalize(expr)expr_str = to_string(normalized) + " ="if expr_str in generated:continuetry:result = compute_value(expr)generated.add(expr_str)exercises.append(expr_str)answers.append(fraction_to_string(*result))except ValueError:pass# 写入题目文件with open('Exercises.txt', 'w') as f:for i, expr in enumerate(exercises, 1):f.write(f"{i}. {expr}\n")# 写入答案文件with open('Answers.txt', 'w') as f:for i, ans in enumerate(answers, 1):f.write(f"{i}. {ans}\n")
  • parse_answer函数
    • 功能:将答案字符串解析为Fraction对象。
    • 根据字符串中是否包含带分数的标识进行不同的解析操作。
def parse_answer(answer_str):answer_str = answer_str.strip()if "'" in answer_str:mixed, frac = answer_str.split("'", 1)whole = int(mixed)numerator, denominator = frac.split('/')return Fraction(whole * int(denominator) + int(numerator), int(denominator))elif '/' in answer_str:numerator, denominator = answer_str.split('/')return Fraction(int(numerator), int(denominator))else:return Fraction(int(answer_str), 1)
  • parse_expression函数
    • 功能:将表达式字符串解析为可用于eval函数计算的Python表达式字符串。
    • 进行一些字符替换(如将×替换为*÷替换为/),然后转换为相应的Python表达式字符串。
def parse_expression(expr_str):expr_str = expr_str.replace(' ', '').replace('×', '*').replace('÷', '/')tokens = re.findall(r"(\d+'\d+/\d+|\d+/\d+|\d+|\+|\-|\*|/|\(|\))", expr_str)converted = []for token in tokens:if token in '()+-*/':converted.append(token)else:converted.append(convert_number(token))return ''.join(converted)
  • convert_number函数
    • 功能:将数字字符串转换为Fraction对象的字符串表示形式。
    • 根据字符串是否为带分数或普通分数进行不同的转换操作。
def convert_number(s):if "'" in s:mixed, frac = s.split("'", 1)numerator, denominator = frac.split('/')return f"Fraction({int(mixed) * int(denominator) + int(numerator)}, {denominator})"elif '/' in s:numerator, denominator = s.split('/')return f"Fraction({numerator}, {denominator})"else:return f"Fraction({s}, 1)"
  • evaluate_expression函数
    • 功能:计算解析后的表达式字符串的值。
    • 使用eval函数在给定的Fraction对象的命名空间下计算表达式的值。
def evaluate_expression(py_expr):try:return eval(py_expr, {'Fraction': Fraction})except:return None
  • check_answers函数
    • 功能:检查答案文件中的答案是否正确。
    • 读取练习题文件和答案文件,对每个练习题解析表达式计算正确答案,解析用户答案,然后比较两者是否相等,最后将正确和错误的题目编号分别写入成绩文件。
def check_answers(exercise_file, answer_file):correct = []wrong = []# 读取题目文件with open(exercise_file, 'r') as f:exercises = [line.strip().split('. ', 1)[1].rstrip(' =') for line in f]  # 提取 "题目内容"# 读取答案文件with open(answer_file, 'r') as f:answers = [line.strip().split('. ', 1)[1] for line in f]  # 提取 "答案内容"if len(exercises) != len(answers):print("错误:题目与答案数量不匹配!")returnfor idx in range(len(exercises)):expr = exercises[idx]user_ans = answers[idx]# 解析表达式并计算正确答案py_expr = parse_expression(expr)correct_ans = evaluate_expression(py_expr)if correct_ans is None:wrong.append(idx + 1)continue# 解析答案try:user_frac = parse_answer(user_ans)except:wrong.append(idx + 1)continue# 比较答案if correct_ans == user_frac:correct.append(idx + 1)else:wrong.append(idx + 1)# 输出结果with open('Grade.txt', 'w') as f:f.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")
  • main函数
    • 功能:解析命令行参数并调用相应的函数(generate_problems或check_answers)。
    • 使用argparse模块解析命令行参数,根据参数调用不同的函数,如果参数不完整则输出错误信息并退出程序。
def main():parser = argparse.ArgumentParser()group = parser.add_mutually_exclusive_group(required=True)group.add_argument("-n", type=int)group.add_argument("-e", type=str)parser.add_argument("-r", type=int)parser.add_argument("-a", type=str)args = parser.parse_args()if args.n is not None:if args.r is None:print("Error: -r required")sys.exit(1)generate_problems(args.n, args.r)elif args.e is not None:if args.a is None:print("Error: -a required")sys.exit(1)check_answers(args.e, args.a)
  1. 流程图

三、性能分析

对于生成模式的性能分析:

对于判断模式的性能分析:

可以看出,生成题目的函数消耗最大(因为分析时让程序生成10000道题目)。

四、单元测试用例

  • 主要分为TestMathGeneratorTestCLICommands两个测试用例类。
  • 单元测试覆盖率:
  1. TestMathGenerator类分析
    • setUptearDown方法
      • setUp中,创建临时目录用于测试文件的操作,备份了原始的命令行参数和标准输出。
      • tearDown方法用于清理临时目录、恢复原始的命令行参数和标准输出,并删除测试过程中可能生成的输出文件。
    • test_number_generation方法
      • 设置随机种子为42,调用generate_number函数生成一个数,然后对生成的数的格式进行验证,确保其是合法的分数形式。
    • test_expression_calculation方法
      • 针对表达式计算函数compute_value进行测试。对加除法乘法三种运算分别构建测试表达式,验证计算结果是否符合预期。
    • test_normalization方法
      • 构建一个复杂的表达式,调用normalize函数对表达式进行规范化,然后使用to_string函数将规范化后的表达式转换为字符串,验证转换后的字符串是否符合预期形式。
    • test_file_generation方法
      • 通过模拟命令行参数(-n-r)调用main函数,然后验证是否成功生成了Exercises.txtAnswers.txt文件,并且文件中的行数是否与预期的数量一致。
    • test_correct_answers方法
      • 创建测试用的题目文件和答案文件内容,调用create_test_files函数创建临时的测试文件,再调用check_answers函数对答案进行检查。最后验证Grade.txt文件是否存在,并检查文件内容中正确答案和错误答案的统计是否符合预期。
    • test_mixed_answers方法
      • 创建包含混合正确/错误答案的题目文件和答案文件内容,创建临时文件并调用check_answers函数。最后验证Grade.txt文件中的正确答案和错误答案统计符合预期。
    • test_cli_error_handling方法
      • 测试命令行缺少必要参数时的错误处理。
  2. TestCLICommands类分析
    • setUptearDown方法
      • 准备测试环境和清理测试后的环境。
    • test_generate_command方法
      • 通过模拟命令行参数(-n-r)调用main函数,验证是否成功生成了Exercises.txt文件,并且文件中的行数是否与-n指定的数量一致,以及每行是否包含=符号。
    • test_check_command方法
      • 先准备测试用的题目文件和答案文件,然后通过模拟命令行参数(-e-a)调用main函数,验证是否成功生成了Grade.txt文件,并且文件内容中正确答案的统计是否符合预期。
    • test_simple_generation方法
      • 创建一个测试文件,验证文件是否存在后将其删除。
    • test_basic_answer_check方法
      • 创建测试用的题目文件和答案文件,然后调用check_answers函数进行答案核对,最后验证是否成功生成了Grade.txt文件。

五、总结与反思

成功之处

  1. 功能实现方面
    实现了基本需求,包括按照指定数量和数值范围生成四则运算题目、确保题目计算过程中不产生负数、除法结果为真分数、题目不重复、生成答案文件、对给定的题目和答案文件进行对错判定等功能。
  2. 测试覆盖方面
    编写了多个测试用例,覆盖了程序的各种功能和边界情况,有效地保证了程序的正确性。

不足之处

  1. 性能优化方面
    虽然进行了一定的性能优化,但在生成大量题目时,程序的运行速度还有提升的空间。
  2. 异常处理方面
    在程序中,部分异常情况的处理还不够完善。比如在处理文件读写异常时,只是简单地给出了一些基本的错误提示,没有更详细的错误信息来帮助用户定位问题。

结对感受

  1. 闪光点
    💙 我们可以同时开展不同的任务:一个人负责设计和编写主要的逻辑代码,另一个人可以同时进行相关功能的测试用例编写。
    💚两个人可以及时互相审查代码。当一个人完成了一部分代码编写后,另一个人可以迅速进行审查,发现潜在的逻辑错误、代码规范问题或者性能瓶颈等。
    😎 我们通过互相学习,不仅提高了自己的技能,还增进了对整个项目的理解,并且可以互相监督对方的进度。
  2. 对彼此的看法
    😄zyj:我的搭档在项目开发的时候充满热情。即使在遇到困难和压力的时候,她也能保持积极的态度,这种热情也感染着我。
    😊 wmq:在这次的作业合作中,我深刻体会到我的搭档的出色之处,她思维缜密,逻辑清晰有条理,且对待工作细致严谨,真是难能可贵的合作伙伴!
  3. 建议
    在结对过程中,沟通的效率还有待提高。在今后的结对项目中,我们应该更加明确地表达自己的想法,并且在讨论之前先对问题进行更深入的思考,以提高沟通的效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/899396.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用ESP32做一个遥控机械臂

RC汽车机械臂只是一个有轮子的RC汽车。该机械臂是一个四自由度机械臂,这意味着它有四个运动部件。我使用mg90伺服电机,因为它们是金属的,但塑料sg90也应该工作。如下图所示。我使用了两个独立的电源,每个都由2S锂离子(Li-on)电池组成,一个用于MCU,另一个用于电机。这是为…

最小化安装Ubuntu

最小化安装 前言: 有时候需要搭建虚拟机,每一次都需要去找文章,搞小半天才能完成环境搭建 这一次写一篇文章记录一下,目的是以后能比较快速简单的搭建好环境 概要 最小化安装需要手动启用网卡 安装防火墙 ⇒ 打开某些端口确保ssh连接等 安装openssh-server openssh-client ⇒ …

愤怒的小鸟

Day 2025/2/20愤怒的小鸟剪切精灵图-改sprite mode为mutiple在sprite editor中slice设置弹弓与鸟的层级关系layer-player 鸟加springjoint组件-distance-0.3-autodistance关闭弹弓组件的一些基本属性:Distance:两点之间的固定距离(设定完有剩余的距离就是可拉伸的长度)Freq…

通过振动传感器,触发水的运动并将其转换为声音

“微挑战”是在巴塞罗那IAAC的“紧急未来设计硕士”课程中为期一周的工作坊。在这一周,我们有时间、空间和专业人士的支持来创建一个功能原型,它也可以是一个投机性的人工制品,有助于我们的个人研究和实践。这个原型应该基于迭代和使用:数字制造工具、生物制造、人工智能、…

单链表练习与重下AS

1.练习题:我的代码答案: /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* reverseList(struct ListNode* head) {struct ListNode dummpy;struct ListNode* tmp;dummpy.next=NULL;//引入临时…

pycharm连接autodl服务器

昨天听舍友说,他们第一个实验都跑完了,瞬间焦虑起来,原来落后这么多,完事昨天晚上7点开始看教程,配环境,看了好几个教程,每个教程都不一样竟然!然后问舍友,舍友说:你先知道你要跑什么项目,我说:我知道啊,我要做什么项目,想要什么结果,我都知道,项目和代码都有,…

shell脚本报错:test.sh: line 2: $\r: command not found

问题 在win上写好shell脚本,传到linux服务器运行调试的时候报错:test.sh: line 2: $\r: command not found 原因 这个错误是由于脚本文件的换行符问题引起的。Windows和Linux系统的换行符不同:Windows使用\r\n(回车+换行)。 Linux使用\n(换行)。脚本是在Windows上编辑的…

Power Apps 技术分享:制作响应式布局

前言Power Apps的一大优势就是可以不用多长时间,就能够配置出响应式布局。正文1.我们先新建一个屏幕,用来演示,如下图:2.添加一个横向容器,修改一下宽和高,根据屏幕尺寸自适应,如下图:逻辑应该蛮好理解的,就是用app的宽,减去当前控件距离顶部的距离的两倍,也就是上下…

day:22 python函数(5)——常用函数

一.len函数 print(len(列表名)) 定义:返回一个内容长度 案例: list=[1,2,3,4,5,6] print(len(list))二.abs 绝对值 print(abs(数值))输出绝对值 案例: a=-9 print(abs(a))三.id() print(id(表名)) 返回一个对象地址,返回对象唯一地址,标示一个整数 list=[1,2,3…

day:22 python函数(4)——文档

一.打开文档 open函数 open() 函数用于打开一个文件,创建一个 file 对象 语法:open(file, mode),模式有r(只读),w(写入覆盖), a(写入追加) (1)查看open所有功能 按住ctrl键,鼠标点击open关键字,查看用法(2)读取python中复制的路径 a.复制py文件的路径 path1=r"C:\U…

day:22 python函数(3)——内置函数和压缩

一.python的内置函数二.内置函数使用 1.format()函数 定义:是一格式化字符串,可以将变量或值插入到字符串的特点位置,使字符串的建构更加的灵活和易读,增强了字符串格式的功能. 2.基本语法 通过{}来代替以前的% 3.案例 a.不设置指定位置,默认顺序 hz="{}{}".format…

Android开发--Lesson01--页面布局

一.View视图 在Android开发中所有的UI元素都是由View和ViewGroup构建而成的。ViewGroup作为一个容器既可以装载View视图空间,同时也可装载ViewGroup。即一种布局可以嵌套另一种布局。二.ViewGroup RelativeLayout RelativeLayout是Android中一种非常灵活的布局方式,它允许子…