结对编程项目-最长英语单词链

作业信息

教学班级:周五班
项目地址:https://github.com/buaddd/buaa_SE_2022_Pair_Program

PSP表格

Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
计划60120
估计这个任务需要多少时间3030
开发12001500
需求分析 (包括学习新技术)180120
生成设计文档180120
设计复审 (和同事审核设计文档)3060
代码规范 (为目前的开发制定合适的规范)3060
具体设计180180
具体编码600600
代码复审180120
测试(自我测试,修改代码,提交修改)300300
报告180180
测试报告120120
计算工作量3060
事后总结, 并提出过程改进计划6060
合计56 * 6060.5 * 60

项目设计与实现

项目结构与UML图

输入输出,建图,异常检查
本项目采用面对对象的思路进行设计,将不同的部分封装成不同类,结构如下所示

  • MyIO: 负责文件的读取与计算结果的输出,保存读入的所有字符串
  • Node, Word_vertex: 分别针对不允许环路与允许环路的情况,保存单词相关的信息,例如首尾的字母,后继节点等内容
  • Generator:根据字符串与参数情况生成Node与Word_vertex对象,建图
  • Checker: 进行环路检测,对于非-r的情况,如果检测到了环路,需要退出程序并报错
    在这里插入图片描述

Core相关计算类
在这里插入图片描述

  • Core: 封装本次作业的计算接口
  • Node_chain_builder与Word_chain_builder: 负责构建单词链,使用graph中的信息,构建单词链
  • Node_graph与Word_graph:保存所有节点,存储相关的信息,供生成者调用,辅助生成单词链
  • Node_chain与Word_chain:存储计算得到的单词链结果
  • Word_tarjan_vertex:代表计算过程中某一类Word_vertex的特殊情况

UML图
在这里插入图片描述

接口设计与实现

Information Hiding,Interface Design,Loose Coupling

  • Information Hiding:类只开放对应的功能接口,但隐藏其数据和实现细节。
  • Interface Design:一个好的接口设计可以使项目的结构更加条理有序。
  • Loose Coupling:处理好类与类之间的通信,避免出现一个类直接出现在另一个类中来及进行数据的交换

计算模块接口的设计与实现过程
本次作业要实现的计算模块接口如下:

int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
int gen_chains_all(char* words[], int len, char* result[]);
int gen_chain_word_unique(char* words[], int len, char* result[]);
int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);

我们将其分为无环与有环两种情况去处理:

  • 对于无环的情况,由于没有环路出现,因此可以将所有类似a…b结构的单词归纳为一类,并存储在Node类中,同时建图过程中存储每个Node的后继Node。
  • 对于有环的情况,上文所述的同结构单词可以重复利用,因此不能再压缩所有单词,需要对每一个单词生成一个Word_vertex类,同时建图过程中存储每个Word_vertex的后继Word_vertex。

针对Node类和Word_vertex类,我们分别设计Node_chain类和Word_chain类来封装两种数据类的链信息,并设计Node_chain_builder类和Word_chain_builder类来实现两种链的构造。

对于接口实现的4种功能,gen_chain_unique 和 gen_chains_all 仅涉及无环情况,只需要调用 Node_chain_builder 类;而 gen_chain_word 和 gen_chain_char 既设计无环情况,又涉及有环情况,需要根据需求选择调用Node_chain_builder类和Word_chain_builder类。

两种builder类的实现结构基本类似,包括init()、build()、get_result()和get_num()这4个基本功能,分别用于初始化、构造对应chain、返回result结果和返回result数组大小

其中,build()内调用一个私有成员函数travelsal_build()来递归遍历每一个节点(Node节点或者Word_vertex节点),每当遇到一个正确的可结束的结尾时,调用相应的update函数来更新目标结果

计算模块接口部分的性能改进
针对无环和有环两种情况,分别给出优化策略

  • 无环的情况:我们使用Node代替Word_vertex来保存单词信息,通过单词的首尾字母在一个26*26的DAG图(类似a…a的情况单独处理)上找到入度为0的点,然后向下遍历。对于gen_chain_word、gen_chain_char和gen_chain_word_unique这种查找最大单词(或字母)数的问题,我们只需要在遍历到出度为0的点时进行更新。经过优化,每个节点只会计算一次。而对于gen_chains_all保存所有单词链的问题,需要进行进一步的优化。
  • gen_chains_all的优化:同样在26*26的DAG图上找到入度0的点向下遍历,每当chain加入一个新的Node时,我们根据cur_chain_all的内容和新节点Node更新cur_chain_all。如果新节点Node的出度等于1,继续向下遍历;如果新节点Node的出度大于1,我们将cur_chain_all压栈,压入cur_chain_all_list,并把cur_chain_all清空,继续向下遍历;如果新节点Node出度等于0,则根据cur_chain_all和cur_chain_all_list栈,生成从开始节点到当前节点的所有单词链。经过优化,每种单词链只会被计算一次。
  • 有环的情况:我们使用Word_vertex来保存单词信息,此时问题转变为”在一个有正环的有向图上求最长路“,是一个典型的NP-Hard问题,不能在多项式时间内被求解。但是我们可以根据无环的思路来优化有环的情况,首先利用tarjan算法进行多点,将Word_vertex节点划分为若干个强连通分量,我们用Word_tarjan_vertex类进行保存,这些由强连通分量(Word_tarjan_vertex)构成的新图是一个DAG图。我们求新DAG图的逆拓扑序列,并按照逆拓扑序列遍历每个强连通分量内的最长路径,并保存每个节点开始的最长路径。因为我们是按照逆拓扑序列遍历每个强连通分量,所以一个强连通分量内如果遍历到其他强连通分量的节点,那个节点的最长路径信息已知,避免了重复计算,从而实现了优化。当然最坏情况和直接遍历所有节点一致——只存在一个强连通分量。
    在这里插入图片描述

Design by Contract,Code Contract
在语言层面上检查precondition前置条件、postcondition后置条件以及Assert断言,和单元测试相关。虽然我们在工程中没怎么得到实现(笑)

测试

计算模块部分单元测试展示
测试数据构造:测试数据构造的思路主要是考虑到覆盖不同的情况,没有追求复杂及较为考验时间复杂的的数据,按照不同参数的分类依次进行测试,例如-n,-m, -w, -w -h a, -w -r等等。同时每一个分类下准备类型不同的测试数据,例如-w -h a这个参数组合准备了如下数据

input: {"bbc", "bbd", "beg", "ccf", "cccccf", "grs", "deg"}
output: null

input: {"abc", "bbd", "ccf", "deg", "grrrrx", "xtq", "qbs"}
output: {"abc", "ccf"}

无环测试数据
有环测试数据

异常处理

项目中遇到异常时,采用输出异常类型至终端并结束程序运行的方式,具体的异常类型如下表所示

异常类型示例异常描述
缺少参数Wordlist.exe -r “input.txt”此时只有-r参数,没有-m, -n -w ,-c中的任一参数,不会有输出结果,因此缺少关键的参数
错误参数组合Wordlist.exe -n -r “input.txt”-n与-r两个参数不能同时存在,此时为错误参数组合
重复参数Wordlist.exe -n -n “input.txt”-n与-n两个参数相同,此时为重复参数
错误参数类型Wordlist.exe -x “input.txt”-x参数不存在,此时为错误参数类型
文件名错误Wordlist.exe -r -w “wrong_file.txt”在尝试读取文件时发生错误,该文件不存在
含单词环输入文本包含abc, cde, eba在参数没有-r的情况下,出现了单词环,此时为错误输入
无有效单词输入文本为 12213,2323123,2144此时无法解析出有效的单词,因此无法构建单词链
无单词链输入文本为 abc, abd此时无法构建出单词链,应当提示文本中无单词链
单词链长度过大-n情况下输出超过20000此时应直接输出长度,并提示超过最大范围,随后返回

环路检测实现
异常的检测大多数通过参数直接判断即可,环路检测采用了拓扑排序去判断

bool judge_circle() {
		int count = 0; //用于计算是否还有节点剩余
		queue<Node*> node_queue;
		//找出所有入度为0的节点
		for (vector<Node*>::iterator it = nodes.begin(); it != nodes.end();it++)
		{
			Node* node = *it;
			if ((node->get_inDegree()) == 0) {
				node_queue.push(node);
				count++;
			}
			//同时判断是否存在多个形如a....a的单词,若存在则必定有环
			if (node->get_start() == node->get_end()) {
				vector<string*> temp_vector;
				node->get_list(temp_vector);
				if (temp_vector.size() > 1) {
					return false;
				}
			}
		}
		//cout << "start" << endl;
		while (!node_queue.empty()) {
			Node* temp = node_queue.front();
			//temp->show_words();
			node_queue.pop();
			char end = temp->get_end();
			//当前节点有后继节点
			if (start_map.count(end) > 0) {
				vector<Node*> list = start_map[end];
				for (vector<Node*>::iterator iter = list.begin(); iter != list.end(); iter++) {
					Node* node = *iter;
					node->sub_inDegree();
					if (node->get_inDegree() == 0) {
						//cout << "next node: " << endl;
						//node->show_words();
						node_queue.push(node);
						count++;
					}
				}
			}
		}
		//cout << count << endl;
		return count == nodes.size();
	}

关于结对编程

结对过程
别人加我
结对图像资料
在这里插入图片描述
《最后的挣扎》
结对编程优点与缺点

  • 优点:思路开阔
  • 缺点:时间不合适,分工不合适

成员的优点与缺点

姓名优点缺点
戴啸天思路清晰,善于表达自己的想法,能够把握大局不怎么干活,特殊情况太多
徐柯轩完成了主要编程的工作,做题有一些神奇的点子,比较重视细节思路比较混乱,没怎么写博客,没学会dll封装(难受)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值