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

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468
这个作业的目标 进行结对开发,掌握结对开发技巧
开发者:彭文昊(3123004192)、齐思贤(3123003122)

Github链接:https://github.com/Jacket-H/Jacket-H
作业Releases链接:https://github.com/Jacket-H/Jacket-H/releases/tag/FormulaGenerator

设计思路

该程序主要有两个功能需要实现:1.随机生成四则运算算式,并控制算式生成的范围和个数;2.能读取算式文件和答案文件,并判断出哪些对哪些错。

为了实现这些功能,我们将其拆分成数个较小的任务:
  1. 文件读取,文件输出
  2. 通过命令行接受参数,分配任务
  3. 随机生成算式
  4. 对生成的算式进行重合判断,确保每个算式都是唯一
  5. 枚举生成算式,这是防止限制范围过小,随机生成效率过低,此时使用枚举生成进行补充
  6. 算式运算
  7. 答案比较

我们注意到应当用某种数据结构来完成分数的运算,算式的保存,同时还要考虑到高效的对算式进行判重。
最后,我们决定使用逆波兰表达式的形式,并建立分数类,和算式节点+vector数组形成的算式链进行保存。

由此又要额外添加如下功能:

1.字符串转换为中序表达式
2.中序表达式转换为字符串
3.中序表达式转换为逆波兰表达式
4.逆波兰表达式转换回中序表达式

程序结构

枚举类型,用于表示算式节点的类型,依次为:数字,加法,减法,乘法,除法,等于,左括号,右括号

typedef enum Formula_Type
{
	NUMBER, ADD, SUB, MUL, DIV, EQU, LBC, RBC
}ftype;

分数类,包含了分数运算,分数比较,通分等操作

typedef struct Fraction {

	int a, b;			//a是分子,b是分母

	int gcd(int x, int y)	//求最大公因数
	{
		while (y)
		{
			int t = y;
			y = x % y;
			x = t;
		}
		return x;
	}
	int Reduction()		//通分
	{
		if (b == 0)
		{
			//std::cout << "ERROR" << std::endl;
			return 0;
		}
		if (a == 0)
		{
			b = 1;
			return 1;
		}

		int g = gcd(abs(a), abs(b));	//最大公因数

		a /= g;
		b /= g;
		if (b < 0)		//确保分母不为负数
		{
			a = -a;
			b = -b;
		}
		return 1;
	}

	Fraction operator+(const Fraction rhs) //分数加法
	{
		Fraction res;
		res.a = a * rhs.b + rhs.a * b;
		res.b = b * rhs.b;
		res.Reduction();
		return res;
	}

	Fraction operator-(const Fraction rhs) //分数减法
	{
		Fraction res;
		res.a = a * rhs.b - rhs.a * b;
		res.b = b * rhs.b;
		res.Reduction();
		return res;
	}

	Fraction operator*(const Fraction rhs) //分数乘法
	{
		Fraction res;
		res.a = a * rhs.a;
		res.b = b * rhs.b;
		res.Reduction();
		return res;
	}

	Fraction operator/(const Fraction rhs) //分数除法
	{
		Fraction res;
		res.a = a * rhs.b;
		res.b = b * rhs.a;

		res.Reduction();
		return res;
	}

	bool operator ==(const Fraction rhs) //分数比较
	{
		if (a == rhs.a&&b == rhs.b) return 1;
		return 0;
	}

	bool operator >(const Fraction rhs) //分数比较
	{
		int x, y;
		x = a * rhs.b;
		y = rhs.a * b;
		return x > y;
	}

	bool operator <(const Fraction rhs) //分数比较
	{
		int x, y;
		x = a * rhs.b;
		y = rhs.a * b;
		return x < y;
	}

	bool operator >=(const Fraction rhs) //分数比较
	{
		int x, y;
		x = a * rhs.b;
		y = rhs.a * b;
		return x >= y;
	}

	bool operator <=(const Fraction rhs) //分数比较
	{
		int x, y;
		x = a * rhs.b;
		y = rhs.a * b;
		return x <= y;
	}

	void make(int x, int y = -1, int z = -1)
	{
		//如果只有一个参数,相当于把整数放进去
		//如果有两个参数,相当于放进去真分数
		//如果三个参数的话,相当于放入带分数(按照源文件输入顺序进行输入)

		if (y == -1 && z == -1) {
			a = x; b = 1;

		}
		else if (z == -1) {
			a = x; b = y;

		}
		else {
			a = x * z + y; b = z;
		}
		Reduction();
		return;
	}

}fraction;

算式节点,表达式的基本单位

typedef struct Formula_Node
{
	ftype type;      //节点类型
	fraction number; //若节点类型为数字,则将数字储存于此

	bool operator<(const Formula_Node& other) const
	{
		return type < other.type;
	}

}fnode;

表达式树,用于生成逆波兰表达式

struct TreeNode {
	fnode data;           // 节点数据(数字或运算符)
	TreeNode* left;       // 左子树
	TreeNode* right;      // 右子树
	fraction value;       // 子树的计算结果

	TreeNode(ftype t, const fraction& num = { 0,1 }) {
		data.type = t;
		if (t == NUMBER) {
			data.number = num;
			value = num;
		}
		else {
			data.number = { 0,1 };  // 运算符不需要数字
		}
		left = right = nullptr;
	}

	~TreeNode() {
		delete left;
		delete right;
	}
};

函数

  1. int File_Read(std::string File_input, std::vector<std::string>&data)
    文件读取

  2. int File_Write(std::string File_output, std::string message)
    文件输出

  3. int String_to_Expression(std::string s, std::vector<fnode>&b)
    字符串转中序表达式

  4. int getPrecedence(ftype type)
    算式优先级计算

  5. int Reverse_Polish_Notation_Conversion(std::vector<fnode>& input, std::vector<fnode> & output)
    中序表达式转逆波兰表达式

  6. int Infix_Expression_Conversion(std::vector<fnode>& input, std::vector<fnode>& output)
    逆波兰表达式转中序表达式

  7. int Reverse_Polish_Notation(std::vector<fnode>&Formula)
    逆波兰表达式去重简化

  8. std::string Expression_to_String(std::vector<fnode>&b)
    中序表达式转字符串

  9. bool Calculate(fraction a, fraction b, ftype type, int r, fraction& res)
    计算合法性检测(两个数加减乘除,并找出不合法的计算)

  10. int Calculate_All(std::vector<fnode>Formula, fraction&res)
    完全计算(计算出整个算式的答案)

  11. int Answer_Judgement(std::string File_exercise, std::string File_checkanswer)
    答案判断(第二个功能的核心)

  12. bool Generate_Random_Numbers(int r, int count, std::vector<fraction>& numbers)
    数字序列随机生成

  13. bool Is_Formula_Valid(const std::vector<fnode>& formula, int r)
    算式合法性检测(检测整个算式是否合法)

  14. TreeNode* Try_Build_Tree_Randomly(const std::vector<fraction>& numbers, int r)
    表达树结构随机生成,用于生成逆波兰表达式

  15. void Post_Order_Traversal(TreeNode* root, std::vector<fnode>& result)
    后续遍历,用于从表达式树形成逆波兰表达式

  16. bool Build_RPN_From_Tree_Random(const std::vector<fraction>& numbers, std::vector<fnode>& formula, int r, int max_attempts = 50)
    表达树随机生成

  17. int Reverse_Polish_Notation_Random(int r, std::vector<fnode>&Formula, int max_total_attempts = 50)
    逆波兰表达式随机生成

  18. void Generate_Number_Sets_Recursive
    数字序列枚举

  19. std::vector<std::vector<fraction>> Generate_All_Number_Sets(int r, int count)
    枚举所有数字序列

  20. void Generate_Operator_Sequences(int depth, int max_depth, std::vector<ftype>& current, std::vector<std::vector<ftype>>& results)
    运算符枚举

  21. std::vector<TreeNode*> Generate_All_Tree_Structures(int num_count)
    表达树结构枚举

  22. void Collect_Tree_Nodes(TreeNode* root, std::vector<TreeNode*>& number_nodes, std::vector<TreeNode*>& operator_nodes)
    归纳表达树节点分为数字节点和运算符节点

  23. bool Try_Build_Expression_With_Tree(TreeNode* tree, const std::vector<fraction>& numbers, const std::vector<ftype>& operators, int range_limit, std::vector<fnode>& result)
    用生成的数字序列和运算符序列填充枚举生成的表达树

  24. void Enumerate_Expressions_For_Numbers(const std::vector<fraction>& numbers, int r, std::set<std::vector<fnode>>& all_formulas, std::vector<std::vector<fnode>>& new_formulas, int max_formulas)
    为数字序列枚举可能的表达式

  25. std::vector<std::vector<fnode>> Enumerate_Remaining_Formulas(int range_limit, const std::set<std::vector<fnode>>& Hash_formulas, int max_formulas = 0)
    逆波兰表达式枚举生成函数

  26. std::vector<std::vector<fnode>> Generate_Formulas_Comprehensive(int r, int n)
    综合生成函数

  27. int Formula_Generation(int n, int r)
    算式生成结果输出函数

  28. int main(int argc, char *argv[])
    主函数

关键代码

感觉80%都是关键代码,1200行贴出来那也太长了


项目小结

  • 这个项目的耗时要远超我的想象,比起自己一个人干,结对工作要有更多事情需要考虑...不过我想更多是因为我们还不太熟练。
    首先,结对工作时思路会更加清晰,一些小错误有另外一个人帮忙纠正,不过也要注意沟通时机的问题,一直保持沟通也会反过来降低工作效率。
    这次也尝试了使用github多人开发的技巧,不过不太熟练,可能反而还降低效率了,需要多花一点时间更加了解这些工具才行。

  • 本人认为这次项目,暴露出本人的一些问题,比如开源平台的运用不熟练等,本人需要更多关注这些项目工具的使用。

#PSP2表格
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 14
Estimate 估计这个任务需要多少时间 650 746
Development 开发 560 601
Analysis 需求分析 (包括学习新技术) 40 30
Design Spec 生成技术文档 60 48
Design Review 设计复审 20 23
Coding Standard 代码规范 (为目前的开发制定合适的规范) 20 10
Design 具体设计 30 0
Coding 具体编码 240 480
Code Review 代码复审 30 10
Test 测试(自我测试,修改代码,提交修改) 120 145
Reporting 报告 90 60
Test Repor 测试报告 40 60
Size Measurement 计算工作量 30 15
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 20 10
posted @ 2025-10-22 22:59  Jacket-H  阅读(34)  评论(0)    收藏  举报