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

news/2025/10/22 21:09:16/文章来源:https://www.cnblogs.com/JiandanMing/p/19158403
这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470
github项目地址 https://github.com/jiandanmingzi/jiandanmingzi/tree/main/3123004657/counter

学号:3123004657 姓名:黄健峰
学号:3123004660 姓名:黄梓聪

1. 效能分析

改进:

(1) 将项目的四则运算式存储方式改为表达式树数组

原存储方式:将所有四则运算式分为两个二维数组,一个数组存储式子中的符号和括号,一个数组存储式子中参与运算的数
修改原因:原存储方式容易容易出现不止一种排列顺序的情况,且验证是否合规所需时间较长

(2) 增加尝试生成运算符号和当前条件下生成符合要求的运算式的次数上限

修改后:当超过预设的次数上限时,会降低要生成的运算式的复杂度(如减少运算符个数,限制括号等)
修改原因:防止生成时间过长

2. 设计实现过程

Myapp.cpp
依赖 FileMana.hpp、CounterGenerator.hpp、AnswerCheck.hpp
负责解析命令行、调度“生成题目/校验答案”、文件读写
CounterGenerator.hpp
依赖 Fraction.hpp
负责按约束随机生成表达式树、去重、输出题目与答案
AnswerCheck.hpp
依赖 Fraction.hpp
负责读取 Exercises.txt/Answers.txt 文本、解析与对比答案
FileMana.hpp
独立文件 I/O 封装,被 Myapp 调用
Fraction.hpp
基础数值类型,提供分数构造、化简与四则运算,供 CounterGenerator 与 AnswerCheck 使用

设计时:根据题目要求分为:FileMana,AnswerCheck,CounterGeneration三个类,在实现过程中,发现AnswerCheck和CounterGeneration对分数的存储和存在形式有共同的需求,因此独立出来一个类Fraction,又因仅CounterGeneration需要对运算式进行特殊存储,因此在CounterGeneration内实现内部类ExprNode;

函数build_random_expr流程图
a470c1b5e56cacea4bd73266ef45a184

函数generate_counters流程图
1c8cbc13fcae3f12d4c5d422fab976c2

函数checkAnswer流程图
e28222c298c91785057831ab1fd8a785

3. 代码说明

/*** @brief 生成随机表达式树* @param ops 运算符数量,决定表达式的复杂度* @param allow_div 是否允许除法运算* @return 表达式树根节点指针*/
ExprNode* build_random_expr(int ops, bool allow_div) {// 基线情况:没有运算符时生成叶子节点(操作数)if (ops == 0) {return make_leaf();}// 随机选择运算符,考虑是否允许除法std::string op = random_operator(allow_div);// 分配运算符给左右子树:总运算符数 = 当前运算符 + 左子树运算符 + 右子树运算符int left_ops = (ops == 1) ? 0 : random_int(0, ops - 1);int right_ops = (ops - 1) - left_ops;// 重试机制:防止因约束条件反复失败const int MAX_TRY = 200;for (int t = 0; t < MAX_TRY; ++t) {// 创建当前运算符节点ExprNode* node = new ExprNode(op, true);ExprNode* L = nullptr;ExprNode* R = nullptr;// 根据不同运算符类型处理子树构建if (op == "+" || op == "*") {// 加法和乘法:直接递归构建左右子树,无特殊约束L = build_random_expr(left_ops, allow_div);R = build_random_expr(right_ops, allow_div);} else if (op == "-") {// 减法:确保左子树值 ≥ 右子树值,避免负数结果L = build_random_expr(left_ops, allow_div);R = build_random_expr(right_ops, allow_div);Fraction lv = L->calculate();Fraction rv = R->calculate();// 检查约束条件,不满足则清理资源并重试if (!frac_ge(lv, rv)) {delete_tree(L);delete_tree(R);delete node;continue;  // 继续重试循环}} else {  // 除法运算 "÷"// 检查是否允许除法,不允许则重新选择运算符if (!allow_div) {delete node;op = random_operator(false);continue;}// 构建右子树并确保其值不为0R = make_positive_subtree(right_ops, allow_div);Fraction rv = R->calculate();if (frac_is_zero(rv)) {delete_tree(R);delete node;continue;}// 构建左子树:需要满足 0 < 左子树值 < 右子树值(真分数条件)bool ok = false;for (int tt = 0; tt < MAX_TRY; ++tt) {if (L) { delete_tree(L); L = nullptr; }L = make_positive_subtree(left_ops, allow_div);Fraction lv = L->calculate();// 检查是否满足真分数条件if (frac_less(lv, rv) && frac_gt_zero(lv)) { ok = true; break; }}// 如果无法找到满足条件的左子树,清理资源并重试if (!ok) {delete_tree(L);delete_tree(R);delete node;continue;}}// 连接子树到当前节点node->left = L;node->right = R;// 最终验证:再次检查运算约束条件(因为递归构建可能有变化)if (op == "-") {Fraction lv = L->calculate();Fraction rv = R->calculate();if (!frac_ge(lv, rv)) {delete_tree(node);  // 删除整个子树continue;}} else if (op == "÷") {Fraction lv = L->calculate();Fraction rv = R->calculate();if (!frac_gt_zero(rv) || !frac_gt_zero(lv) || !frac_less(lv, rv)) {delete_tree(node);continue;}}// 所有约束条件满足,返回构建的表达式树return node;}// 重试机制失败后的退化处理:生成简单叶子节点// 避免因过于严格的约束条件导致无法生成表达式return make_leaf();
}

设计思路:

  1. 递归构建:根据运算符数量递归构建左右子树
  2. 约束保证:确保减法结果非负,除法结果为真分数
  3. 重试机制:对可能失败的运算类型设置重试次数限制
  4. 退化处理:多次失败后生成简单叶子节点作为保底
/*** @brief 生成题目*/
void generate_counters() {// 清理历史数据,准备新一轮生成for (auto* p : expr_trees) delete_tree(p);expr_trees.clear();answers.clear();// 使用集合记录已生成的表达式模式,用于去重std::set<std::string> seen;// 根据数值范围决定是否允许除法(当range小于2时,无法生成有效除法)bool allow_div = (range > 2);// 设置全局最大尝试次数,防止无限循环const int MAX_GLOBAL_TRY = std::max(count * 50, 200);int attempts = 0;// 主生成循环:尝试生成复杂表达式(1-3个运算符)while ((int)expr_trees.size() < count && attempts < MAX_GLOBAL_TRY) {attempts++;// 随机选择运算符数量(1-3个)int ops = random_int(1, 3);ExprNode* root = build_random_expr(ops, allow_div);/*** 深度验证函数:递归检查整个表达式树的合法性* 特别是确保所有层级的减法和除法都满足约束条件* build_random_expr 只保证顶层运算合法,但子表达式可能存在问题*/std::function<bool(ExprNode*)> validate = [&](ExprNode* n)->bool{if (!n || !n->isOperator) return true;  // 叶子节点总是合法的// 递归验证左右子树bool okL = validate(n->left);bool okR = validate(n->right);if (!okL || !okR) return false;// 检查当前节点的运算约束if (n->value == "-") {Fraction lv = n->left->calculate();Fraction rv = n->right->calculate();if (!frac_ge(lv, rv)) return false;  // 减法:左值 ≥ 右值} else if (n->value == "÷") {Fraction lv = n->left->calculate();Fraction rv = n->right->calculate();// 除法:分母不为0,分子分母都为正,且为真分数if (!frac_gt_zero(rv) || !frac_gt_zero(lv) || !frac_less(lv, rv)) return false;}return true;};// 如果验证失败,清理并重试if (!validate(root)) {delete_tree(root);continue;}/*** 去重检查:通过规范化表达式检测等价表达式* 规范化包括:排序交换律运算、统一格式等* 例如: "1+2" 和 "2+1" 会被识别为重复*/ExprNode* norm = clone_tree(root);norm = norm->normalize();  // 规范化表达式树std::string key = norm->to_string();  // 生成规范化字符串作为唯一标识delete_tree(norm);// 检查是否已生成过等价表达式if (seen.find(key) != seen.end()) {delete_tree(root);continue;}// 保存新的唯一表达式seen.insert(key);expr_trees.push_back(root);}/*** 补充生成阶段:如果复杂表达式生成不足,改用简单表达式* 使用1个运算符的表达式更容易生成且更容易满足约束条件*/while ((int)expr_trees.size() < count) {int ops = 1;  // 只使用1个运算符,降低复杂度ExprNode* root = build_random_expr(ops, allow_div);// 同样的去重检查流程ExprNode* norm = clone_tree(root);norm = norm->normalize();std::string key = norm->to_string();delete_tree(norm);if (seen.insert(key).second) {expr_trees.push_back(root);} else {delete_tree(root);}// 安全阀:如果尝试过多仍无法生成足够题目,提前退出// 避免因过于严格的约束条件导致无限循环if ((int)seen.size() > count * 5) break;}
}

设计思路:

  1. 去重机制:通过规范化表达式和字符串比较避免重复题目
  2. 合法性验证:确保所有减法、除法运算满足数学约束
  3. 渐进策略:先尝试复杂表达式,失败后降级为简单表达式
  4. 资源管理:妥善处理内存,避免泄漏

4. 测试运行

前五次测试输入的参数:

871a07f2f7c61b8f0e5c8e3b629f232b

前五次测试的输出结果(生成的txt文件截图)

67e1d6470f379fdc46d19c839736a62f
62e85303c7cae253cc75a248205e2508
980db37e5a3d19a3f4306b6eee6135a4
dbba21d69b003972b8f992c73f29da72
252d283cfd8f6728f571ca11568fde89

后五次测试输入的参数:

749a25190ebfa4cb417321d1de906e2c

后五次测试的输出结果(生成的txt文件截图):

d9ef7ee461d3caaeb08b154ea15b29d2
bf32d423d04c8baa5df25fb00dd2a1de
697e78b793a3fbd56177b6280ce43289
e51c006022891334a60fbb25ea4f328e
50737de8c53c7d2ff363da58dfbd1a26

确认程序正确的原因:

截图中的所有四则运算均经过人工检查,确认符合题目需求
截图中的所有四则运算均人工进行了计算,得出的结果与程序给出的答案一致
检验答案产生的结果经过人工对照,完全符合现实

5. PSP表格

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

6.小结

结对感受:设置github允许别人上传过程复杂,因为第一次结对开发,两人对git的共同开发使用也不是很熟练,分工后也因为完成设计途中发现原设计有问题然后修改设计方案导致分工出问题,又因为开始码代码的时间较晚,所以最终采用了两人分开时间开发,一方开发完成后将新文件发给另一方这种低效的形式进行开发;因此,下次开发前,要学习并熟练掌握git的多人开发,同时在设计的时候去查证拟定的开发思路是否有问题。

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

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

相关文章

数据分析工具Pandas

View Post数据分析工具Pandas1、Pandas的数据结构分析 (1)Series:是一个类似一维数组的对象,它能够保存任何类型的数据,主要由一组数据和与之相关的索引两部分分构成;为了能方便地操作Series对象中的索引和数据,…

switch的简单运用

switch 分支语法 switch 分支结构用于在多个可能的情况下选择一种情况进行处理。以下是 switch 分支结构的基本语法:switch(变量表达式){case 常量1:语句;break;case 常量2:语句;break;case 常量3:语句;break;...case …

科学计算库Numpy

View Post科学计算库Numpy1、认识Numpy数组对象 Numpy中最重要的一个特点就是其N维数组对象,即ndarray对象,该对象可以执行一些科学计算。点击查看代码 #导入库 import numpy as np2、创建Numpy数组 (1)最简单的创…

实用指南:基于蜣螂优化的LSTM深度学习网络模型(DBO-LSTM)的一维时间序列预测算法matlab仿真

实用指南:基于蜣螂优化的LSTM深度学习网络模型(DBO-LSTM)的一维时间序列预测算法matlab仿真2025-10-22 21:02 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !import…

10.22总结

1.今天学习了数据库增添数据 2.明天准备学习删除数据 3.今天不太熟悉

AutoGen框架入门:5个核心概念搭建智能体协作系统

AutoGen 是微软研究院开源的多智能体 AI 系统框架。这个框架的设计思路很简单——让多个 AI 智能体(加上人类参与)在对话中完成复杂任务的协作和推理。 你甚至可以把它理解成一个团队聊天室,智能体们在里面讨论、争…

使用google上colab编辑器

1.先下载google,并注册google账号 手机注册失败时,可以在浏览器设置中把浏览器语言改为中文,重新注册即可 2.登录google drive 网址:https://drive.google.com/drive/my-drive 3.在google drive 中加载 Colab Noteb…

英语_阅读_The power of curiosity_待读

The power of curiosity 好奇心的力量 Curiosity is one of the most powerful forces that drive human progress. 好奇心是推动人类进步最强大的力量之一。 It is the desire to learn, explore and understand the …

20251022周三日记

20251022周三日记今日: 1.昨晚很不爽啊,越想越来气,最终想到无欲则刚,好死不如赖活着。 2.早上起床来趟实验室把电脑拿起来去上课,拜托陈和隋帮忙听CSC,感觉确实都是给博士的,可以先从语言入手。低能伦理课配ge…

goden-eye 靶场

开启靶场 端口探测 nmap nmap -sS -sV -T5 -A IP开启的80http与25端口, 访问一下80端口让我们访问/sev-home/ 这个页面登录 登录页面我们可以选择暴力破解 爆破资源太少,先试着获取一下敏感信息 f12有这样一个页面,有一…

20232424 2025-2026-1 《网络与系统攻防技术》实验二实验报告

20232424 2025-2026-1 《网络与系统攻防技术》实验二实验报告 1.实验内容 1.使用netcat在windows物理机获取远程主机kali的Shell,cron启动任务 2.使用socat在kali获取物理主机的Shell, 启动任务计划 3.使用MSF meterp…

记录docker desktop wsl2奔溃的查询思路

错误 无规律的间接性出现奔溃信息 There was a problem with WSL An error occurred while running a WSL command. Please check your WSL configuration and try again.running wslexec: An error occurred while ru…

股票操作统计分析报告 - 2025年10月22日

股票操作统计分析报告body { font-family: "Microsoft YaHei", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: rgba(51, 51, 51, 1); max-width: 1000px; margin: …

软工结对作业

软件工程结对作业项目 内容所属课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/作业要求 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470作业目标 完成…

20232419 2025-2026-1《网络与系统攻防技术》实验二实验报告

一、实验内容 由于在不同的网络条件下多次尝试,因此IP会有所不同 (1)使用netcat获取主机操作Shell,cron启动某项任务(任务自定) PS:cron是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程 (2)使…

dfs模板(p1036)

P1036 [NOIP 2002 普及组] 选数 题目描述 已知 \(n\) 个整数 \(x_1,x_2,\cdots,x_n\),以及 \(1\) 个整数 \(k\)(\(k<n\))。从 \(n\) 个整数中任选 \(k\) 个整数相加,可分别得到一系列的和。例如当 \(n=4\),\(k…

leetcode477. 汉明距离总和

leetcode477. 汉明距离总和477. 汉明距离总和🤡过不了的暴力解:class Solution {public int totalHammingDistance(int[] nums) {int n = nums.length,res = 0;for(int i = 0;i < n;++i){for(int j = i + 1;j &l…

Java中的修饰符

在 Java 中,修饰符(Modifiers)是用来修改类、方法、变量、构造器等定义的关键字。它们主要分为两大类:访问修饰符和非控制修饰符。 以下是 Java 中所有修饰符的详细分类和说明: 一、 访问修饰符 (Access Modifier…

CF2078D Scammy Game Ad

设 \(f_{i, j}\) 为第 \(j\) 道的小人选择了第 \(i\) 个门,从 \(i \sim n\) 能够对答案产生多少贡献,倒着转移即可。