软件工程第三次作业-结对项目
| 这个作业属于哪个课程 | 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.能读取算式文件和答案文件,并判断出哪些对哪些错。
为了实现这些功能,我们将其拆分成数个较小的任务:
- 文件读取,文件输出
- 通过命令行接受参数,分配任务
- 随机生成算式
- 对生成的算式进行重合判断,确保每个算式都是唯一的
- 枚举生成算式,这是防止限制范围过小,随机生成效率过低,此时使用枚举生成进行补充
- 算式运算
- 答案比较
我们注意到应当用某种数据结构来完成分数的运算,算式的保存,同时还要考虑到高效的对算式进行判重。
最后,我们决定使用逆波兰表达式的形式,并建立分数类,和算式节点+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;
}
};
函数
-
int File_Read(std::string File_input, std::vector<std::string>&data)
文件读取 -
int File_Write(std::string File_output, std::string message)
文件输出 -
int String_to_Expression(std::string s, std::vector<fnode>&b)
字符串转中序表达式 -
int getPrecedence(ftype type)
算式优先级计算 -
int Reverse_Polish_Notation_Conversion(std::vector<fnode>& input, std::vector<fnode> & output)
中序表达式转逆波兰表达式 -
int Infix_Expression_Conversion(std::vector<fnode>& input, std::vector<fnode>& output)
逆波兰表达式转中序表达式 -
int Reverse_Polish_Notation(std::vector<fnode>&Formula)
逆波兰表达式去重简化 -
std::string Expression_to_String(std::vector<fnode>&b)
中序表达式转字符串 -
bool Calculate(fraction a, fraction b, ftype type, int r, fraction& res)
计算合法性检测(两个数加减乘除,并找出不合法的计算) -
int Calculate_All(std::vector<fnode>Formula, fraction&res)
完全计算(计算出整个算式的答案) -
int Answer_Judgement(std::string File_exercise, std::string File_checkanswer)
答案判断(第二个功能的核心) -
bool Generate_Random_Numbers(int r, int count, std::vector<fraction>& numbers)
数字序列随机生成 -
bool Is_Formula_Valid(const std::vector<fnode>& formula, int r)
算式合法性检测(检测整个算式是否合法) -
TreeNode* Try_Build_Tree_Randomly(const std::vector<fraction>& numbers, int r)
表达树结构随机生成,用于生成逆波兰表达式 -
void Post_Order_Traversal(TreeNode* root, std::vector<fnode>& result)
后续遍历,用于从表达式树形成逆波兰表达式 -
bool Build_RPN_From_Tree_Random(const std::vector<fraction>& numbers, std::vector<fnode>& formula, int r, int max_attempts = 50)
表达树随机生成 -
int Reverse_Polish_Notation_Random(int r, std::vector<fnode>&Formula, int max_total_attempts = 50)
逆波兰表达式随机生成 -
void Generate_Number_Sets_Recursive
数字序列枚举 -
std::vector<std::vector<fraction>> Generate_All_Number_Sets(int r, int count)
枚举所有数字序列 -
void Generate_Operator_Sequences(int depth, int max_depth, std::vector<ftype>& current, std::vector<std::vector<ftype>>& results)
运算符枚举 -
std::vector<TreeNode*> Generate_All_Tree_Structures(int num_count)
表达树结构枚举 -
void Collect_Tree_Nodes(TreeNode* root, std::vector<TreeNode*>& number_nodes, std::vector<TreeNode*>& operator_nodes)
归纳表达树节点分为数字节点和运算符节点 -
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)
用生成的数字序列和运算符序列填充枚举生成的表达树 -
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)
为数字序列枚举可能的表达式 -
std::vector<std::vector<fnode>> Enumerate_Remaining_Formulas(int range_limit, const std::set<std::vector<fnode>>& Hash_formulas, int max_formulas = 0)
逆波兰表达式枚举生成函数 -
std::vector<std::vector<fnode>> Generate_Formulas_Comprehensive(int r, int n)
综合生成函数 -
int Formula_Generation(int n, int r)
算式生成结果输出函数 -
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 |

浙公网安备 33010602011771号