项目 | 内容 |
---|---|
所属课程 | 2019春季计算机学院软件工程(任健) |
所属做业 | 结对项目-最长单词链 |
课程目标 | 理解软件工程的做用和重要性,提高工程能力,团队协做能力 |
做业目标 | 实战双人结对编程 |
https://github.com/sephyli/wordlist_BUAAgit
Personal Software Process Stages | PSP2.1 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
计划 | Planning | ||
· 估计这个任务须要多少时间 | · Estimate | 5 | 0 |
开发 | Development | ||
· 需求分析 (包括学习新技术) | · Analysis | 80 | 60 |
· 生成设计文档 | · Design Spec | 0 | 0 |
· 设计复审 (和同事审核设计文档) | · Design Review | 0 | 0 |
· 代码规范 (为目前的开发制定合适的规范) | · Coding Standard | 10 | 0 |
· 具体设计 | · Design | 90 | 60 |
· 具体编码 | · Coding | 180 | 120 |
· 代码复审 | · Code Review | 120 | 180 |
· 测试(自我测试,修改代码,提交修改) | · Test | 120 | 180 |
报告 | Reporting | ||
· 测试报告 | · Test Report | 60 | 20 |
· 计算工做量 | · Size Measurement | 0 | 0 |
· 过后总结, 并提出过程改进计划 | · Postmortem & Process Improvement Plan | 0 | 0 |
合计 | 665 | 620 |
在面向对象程序的设计中,有不少的类在实现中会有一些本身独有的属性成员或函数。这样的属性可能和类的功能正确的运行有着千丝万缕的联系。而这样的属性是不该该由外部类进行访问和修改的,属于类的私有属性,即隐藏了类的信息,作到了Information Hiding。github
在咱们的程序中,咱们将一些类在运行过程当中的控制变量设为私有并经过函数来访问或修改。从而符合了这一思想。算法
咱们按照做业的要求,设计了int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
,与int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
两个核心功能的接口并封装为DLL。编程
咱们在设计时,每一个类都的逻辑都仅仅与本身的属性成员相关联。如计算模块,只接受字符串集和模式信息就会进行计算。则设计任何的Input方式均可以与计算模块进行交互。数据结构
咱们在拿到题目后,第一反应是直接使用暴力深搜解决问题。可是看到程序正确性要求中的300s运行时间限制(尽管咱们如今也没能弄明白是针对-w
模式,-c
模式,仍是二者兼有),咱们意识到纯粹使用深搜是不合理的。在对算法进行必定的分析以后,咱们意识到,不遍历全部知足条件的单词链,是不可能找出其中知足要求的最长/最多单词链的。也就是说,深度优先搜索,将会是咱们必需要使用的算法。app
完成这部分分析以后,咱们将优化的视角投向了数据结构部分。咱们注意到,程序要求单词链知足以下条件。函数
单词链的定义为:由至少2个单词组成,前一单词的尾字母为后一单词的首字母,且不存在重复单词oop
这就意味着,咱们在从文件中读取单词的时候就该避免重复单词的读入。不只如此,咱们还设计了独到的数据结构来放置单词,从而实现了访查效率的最大化。单元测试
举例来讲,咱们在WordSet
类中,设置了26*26的二位vector
来放置头字母相同、尾字母相同的单词,并在vector
中按照单词的长度来排列,这样的话,无论对于-w
模式仍是-c
模式,均可以采起同一套访查算法。不只如此,如此组织数据结构,可让咱们的核心搜索函数(深搜函数)快速经过找到以目标字母开头的单词,相比传统的深度优先搜索,咱们在这一步的复杂度从O(N)
降到了O(1)
。考虑到深搜核心函数的调用次数是一个随着单词链长度增加而成阶乘级增加的,咱们的优化方法,应该来讲也是有必定做用的。学习
char head
: 头字母char tail
:尾字母int length
:单词长度std::string s
:单词bool use
:标志是否被使用Word(const char* s, int length)
:构造函数std::vector<Word> set[26][26]
:单词组void append(Word w)
:添加单词bool recurMode
:单词成环模式bool headMode
:头字母指定模式bool tailMode
:尾字母指定模式bool wordNumMaxMode
:单词数量最大模式bool charNumMaxMode
:单词字母最多模式char head
:指定的头字母char tail
:指定的尾字母void append(Word w)
:添加单词Data data
:数据信息Mode mode
:模式信息std::vector<Word> maxWordList
:历史max单词liststd::vector<Word> tmpWordList
:当前max单词listbool exe()
:执行函数在构造数据结构时,我将问题的思惟模式转变为一种带权重的有向图。如hello,即结点h到结点o的有向边,若-w模式,权重为1,-c则为5。那么我存下了每一个节点到另外一个节点的边的信息并以节点号进行索引,构造出了vector<Word> WordSet[26][26]
这样的结构,每一个vector
内用权重进行排序。
由此问题便转变为了避免容许重复节点和路径的最长路径问题。考虑过DP,但没法构造高效的子问题,及子问题合并时须要判断重复的路径和节点,猜想并不会提高效率。由此使用了深度优先搜索,在非-r模式,的复杂度约为26!这个数量级,所以构造出了最复杂的样例后,是绝无可能在300s以内完成计算的。由此放弃了在整体算法层次上的优化,仅仅追求最短的搜索路径和较小的访存开销。
结果: 算法在-r模式下的100个词中,能够在20ms内完成单词链搜索。
该模式的核心在于一个类应拥有的不变式,以及每一个成员函数在运行先后须要保证不变式为真。
咱们在实现Word
类时,保证了head
, tail
, length
等属性与单词自己相匹配。
在实现WordSe
t类时,保证了每一个vector
内的Word
都是从长到短排序,从而能够便捷的找到第一个未被使用的最长单词。
./test/testfile.txt
文件中包含一个连续长度超过100的字母串。TEST_METHOD(TestTooLongWord) { FILE *fin; fopen_s(&fin, "../test/testfile.txt", "r"); char *words[10000], *result[105]; Inputer *inputer = new Inputer(); int wordNum = inputer->getWord(fin, words); for (int i = 0; i < 105; i++) { result[i] = new char(100); } int len = 0; len = Core::gen_chain_word(words, wordNum, result, 0, 0, false); }
./test/testfile.txt
文件中包含若干个能够构成单词环的单词。TEST_METHOD(TestNonRecureFalse) { FILE *fin; fopen_s(&fin, "../test/testfile.txt", "r"); char *words[10000], *result[105]; Inputer *inputer = new Inputer(); int wordNum = inputer->getWord(fin, words); for (int i = 0; i < 105; i++) { result[i] = new char(100); } int len = 0; len = Core::gen_chain_word(words, wordNum, result, 0, 0, false); }
./test/testfile.txt
文件中包含若干个能够构成单词环的单词。TEST_METHOD(TestNonRecureFalse) { FILE *fin; fopen_s(&fin, "../test/testfile.txt", "r"); char *words[10000], *result[105]; Inputer *inputer = new Inputer(); int wordNum = inputer->getWord(fin, words); for (int i = 0; i < 105; i++) { result[i] = new char(100); } int len = 0; len = Core::gen_chain_word(words, wordNum, result, 0, 0, false); }
./test/testfile.txt
文件中仅包含一个单词,或所包含的单词没法造成长度超过1的单词链。TEST_METHOD(TestNonRecureFalse) { FILE *fin; fopen_s(&fin, "../test/testfile.txt", "r"); char *words[10000], *result[105]; Inputer *inputer = new Inputer(); int wordNum = inputer->getWord(fin, words); for (int i = 0; i < 105; i++) { result[i] = new char(100); } int len = 0; len = Core::gen_chain_word(words, wordNum, result, 0, 0, false); }
./test/testfile.txt
文件不存在。TEST_METHOD(TestNonRecureFalse) { FILE *fin; fopen_s(&fin, "../test/testfile.txt", "r"); char *words[10000], *result[105]; Inputer *inputer = new Inputer(); int wordNum = inputer->getWord(fin, words); for (int i = 0; i < 105; i++) { result[i] = new char(100); } int len = 0; len = Core::gen_chain_word(words, wordNum, result, 0, 0, false); }
程序支持经过命令行的方式输入参数以及文件位置信息。参数及其约定以下。
参数名字 | 参数意义 | 范围限制 | 用法示例 |
---|---|---|---|
-w |
须要求出单词数量最多的单词链 | 绝对或相对路径 | 示例:Wordlist.exe -w input.txt [表示从input.txt 中读取单词文本,计算单词数量最多的单词链] |
-c |
须要求出字母数量最多的单词链 | 绝对或相对路径 | 示例:Wordlist.exe -c input.txt [表示从input.txt 中读取单词文本,计算字母数量最多的单词链] |
-h |
指定单词链首字母 | a-z ,A-Z |
示例:Wordlist.exe -h a -w input.txt [表示从input.txt 中读取单词文本,计算知足首字母为a 的、单词数量最多的单词链] |
-t |
指定单词链尾字母 | a-z ,A-Z |
示例:Wordlist.exe -t a -c input.txt [表示从input.txt 中读取单词文本,计算知足尾字母为a 的、字母数量最多的单词链] |
-r |
容许单词文本中隐含单词环 | NONE |
示例:Wordlist.exe -r -w input.txt [表示从input.txt 中读取单词文本,计算单词数量最多的单词链,即便单词文本中隐含单词环也须要求解] |
程序将命令行参数的提取部分放在了main函数中,经过引用int main(int agrc, char* agrv[])
函数的方式,将命令行参数的数量读取在agrc
中,分词读取在agrv[]
中。
经过判断分词是否为-
开头,来判断该词是否表明着命令行参数,在经过该词的第二个字母,判断具体属于哪一个命令行参数(支持大小写)或者报错。同时经过判断第三个字母是否为\0
来判断参数是否过长。若是为参数有后续的范围限制,如-h
、-t
后须要指定开头、结尾的字母,则继续判断字母是否符合要求(个数为一个且在有意义)。
所有判断完成后,将判断的结果传入构造的Mode
对象中,为后续程序所使用。
具体来看,程序声明了以下变量,在对不一样参数作解析的时候,就针对不一样状况对变量进行赋值,这样就收集所有的命令行参数信息。
bool rMode = false; bool hMode = false; bool tMode = false; bool wMaxMode = false; bool cMaxMode = false; char h = 0; char t = 0; char filePath[1000] = "\0";
再经过对Mode
对象的赋值,从而实现了命令行模式的判断。
Mode *mode = new Mode(); mode->Set(rMode, hMode, tMode, wMaxMode, cMaxMode, h, t);
缺点:双方较难统一时间,在开发进程中会有所延误。
双人一块儿进行编程,虽然相对于一我的效率有所提高,但分开编程可能会提供更多的生产力。即便减去两人的沟通成本。
优势:在设计时同步进行Code Review,大大减小了Bug的出现几率。每每一人在Coding的同时,另外一人就会在旁边直接提醒代码出现的问题,提高了单人编程的效率。
集两人的想法于一身,在思惟的碰撞中产生更好的算法和更精妙的数据结构。
在开发时减小放空,有领航员来督促推动进度。有时在Coding时由于想不起来某个类的一个方法,忽然就停住了,这时候若是被其余事情打断,就会去作别的事儿。结对编程时,就会有伙伴进行直接提醒,减小了相似状况的发生。