項目 | 內容 |
---|---|
所屬課程 | 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時由於想不起來某個類的一個方法,忽然就停住了,這時候若是被其餘事情打斷,就會去作別的事兒。結對編程時,就會有夥伴進行直接提醒,減小了相似狀況的發生。