PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 40 |
· Estimate | · 估計這個任務須要多少時間 | 30 | 40 |
Development | 開發 | 1750 | 2365 |
· Analysis | · 需求分析 (包括學習新技術) | 240 | 300 |
· Design Spec | · 生成設計文檔 | 60 | 150 |
· Design Review | · 設計複審 | 10 | 10 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 20 | 15 |
· Design | · 具體設計 | 180 | 90 |
· Coding | · 具體編碼 | 840 | 1320 |
· Code Review | · 代碼複審 | 280 | 120 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 120 | 360 |
Reporting | 報告 | 135 | 140 |
· Test Repor | · 測試報告 | 60 | 30 |
· Size Measurement | · 計算工做量 | 15 | 20 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 60 | 90 |
合計 | 1915 | 2545 |
解題思路大體將這4項小問題歸爲3類來解決。html
統計字符數:只須要統計Ascii碼,漢字不需考慮,
空格,水平製表符,換行符,均算字符。node
統計文件的有效行數:任何包含非空白字符的行,都須要統計。python
統計文件的單詞總數,單詞:至少以4個英文字母開頭,跟上字母數字符號,單詞以分隔符分割,不區分大小寫。ios
統計文件中各單詞的出現次數,最終只輸出頻率最高的10個。頻率相同的單詞,優先輸出字典序靠前的單詞。git
爲了獨立需求中的三項功能,因此我在代碼文件上的組織也將這三項功能封裝到不一樣的cpp文件中,而且在頭文件中聲明各自的函數。 github
work_2.h
——包含頭文件、數據結構以及用到函數的聲明。Count_chrs.cpp
——統計字符數模塊(也包含行數的統計)Count_words.cpp
——統計單詞數模塊(結果計入hashmap)Rank_words.cpp
——詞頻字典序導出模塊
各個模塊能夠分開進行單元測試,也能夠合併一塊兒做爲最終的輸出結果。正則表達式
Rank_words.cpp
包含3個函數用於實現hashmap
031602509 |- src |- WordCount.sln |- UnitTest1 |- WordCount |- Count_chrs.cpp |- Count_words.cpp |- Rank_words.cpp |- WordCount.cpp |- WordCount.vcxproj |- pch.cpp |- pch.h |- work_2.h
具體文件組織以下所示編程
整個需求完成的流程圖以下所示。
ubuntu
詳細正則表達式判斷過程在下文中以流程圖形式展現windows
敲重點!!!查閱了一些文檔,發現VS2017不支持零寬斷言判斷,因此使用正則表達式須要額外增長分隔符的判斷
具體代碼以下所示:(性能改進後)
int C_words(istream &fl, Words &wn, Wordnode **l) { int count = 0; int flag = 0; regex pattern(".[a-zA-Z]{3}[a-zA-Z0-9]*"); //設定正則表達式模板 smatch result; //smatch類存放string結果 //cout << regex_search(wn.all_string, result, pattern)<<endl; string::const_iterator start = wn.all_string.begin(); //字符串起始迭代器 string::const_iterator end = wn.all_string.end(); //字符串末尾迭代器 string temp_str; while (regex_search(start, end, result, pattern)) //循環搜索匹配模板的單詞 { flag = 0; //cout<<"successfully match"; temp_str = result[0]; if (!((temp_str[0] <= 90 && temp_str[0] >= 65) || (temp_str[0] <= 122 && temp_str[0] >= 97)))//首字符判斷 { if (temp_str[0] >= 48 && temp_str[0] <= 57) //數字首字符判斷 flag = 1; temp_str.erase(0, 1); if (!(temp_str.size()>=4&&((temp_str[3] <= 90 && temp_str[3] >= 65) || (temp_str[3] <= 122 && temp_str[3] >= 97)))) { flag = 1; } } if (flag == 0) { transform(temp_str.begin(), temp_str.end(), temp_str.begin(), ::tolower);//轉換爲小寫單詞 hash_insert(l, temp_str); //哈希節點插入 count++; } start = result[0].second; //檢測下一單詞 } //cout << endl; return count; } }
修改後結果以下圖所示
設定了12個單元測試用於測試代碼,具體以下所示。
單元測試內容 | 測試模塊 | 輸出結果 | 測試效果 |
---|---|---|---|
給定一個字符串 | 字符統計 | 字符數 | 經過 |
給定論文部份內容A | 單詞統計 | 單詞數 | 經過 |
給定論文部份內容B | 詞頻統計 | 詞頻前10排名 | 經過 |
非法參數 | 容錯檢測 | 錯誤提示 | 經過 |
輸入文件異常 | 容錯檢測 | 錯誤提示 | 經過 |
輸出文件異常 | 容錯檢測 | 錯誤提示 | 經過 |
給定部分文本內容與部分無效行 | 有效行判斷 | 有效行數 | 經過 |
給定相近字符串 | 單詞統計、詞頻統計 | 詞頻前10排名、單詞數 | 修改代碼後經過 |
給定存在大小寫區別的字符串 | 單詞統計、詞頻統計 | 詞頻前10排名、單詞數 | 經過 |
給出「File123」與「123File」 | 單詞統計、詞頻統計 | 詞頻前10排名、單詞數 | 修改代碼後經過 |
給出多個不合規範字符串 | 單詞統計 | 單詞數 | 經過 |
給出相似亂碼文檔 | 單詞統計、詞頻統計、有效行統計、字符統計 | 所有需求 | 經過 |
namespace WordCount_Test { TEST_CLASS(UnitTest1) { public: TEST_METHOD(TestMethod6) { // TODO: 在此輸入測試代碼 File fnew; //控制文件模塊 Words wnew; //控制單詞模塊 Wordnode *log[HASH_LENGTH] = { NULL }; //哈希散列指針數組 strcpy_s(fnew.file_name, "F:/VS_project/WordCount/WordCount_Test/test/test6.txt"); //獲取文件名 //cout << fnew.file_name << endl; ifstream f; f.open(fnew.file_name, ios::in); //打開文件 if (!f.is_open()) //檢測文件是否存在 { cout << "can't open this file!" << endl; } fnew.count_chars = C_chars(f, fnew, wnew); fnew.count_words = C_words(f, wnew, log); //計算單詞數(插入哈希節點) rank_word(log, wnew); //詞頻排名 //單詞需按字典序排列纔可,依次檢測排序。 Assert::AreEqual(wnew.word_rank[1], string("ubuntu14")); Assert::AreEqual(wnew.count_rank[1], 1); Assert::AreEqual(wnew.word_rank[2], string("ubuntu16")); Assert::AreEqual(wnew.count_rank[2], 1); Assert::AreEqual(wnew.word_rank[3], string("windows")); Assert::AreEqual(wnew.count_rank[3], 1); Assert::AreEqual(wnew.word_rank[4], string("windows2000")); Assert::AreEqual(wnew.count_rank[4], 1); Assert::AreEqual(wnew.word_rank[5], string("windows97")); Assert::AreEqual(wnew.count_rank[5], 1); Assert::AreEqual(wnew.word_rank[6], string("windows98")); Assert::AreEqual(wnew.count_rank[6], 1); } TEST_METHOD(TestMethod7) { // TODO: 在此輸入測試代碼 File fnew; //控制文件模塊 Words wnew; //控制單詞模塊 Wordnode *log[HASH_LENGTH] = { NULL }; //哈希散列指針數組 strcpy_s(fnew.file_name, "F:/VS_project/WordCount/WordCount_Test/test/test7.txt"); //獲取文件名 //cout << fnew.file_name << endl; ifstream f; f.open(fnew.file_name, ios::in); //打開文件 if (!f.is_open()) //檢測文件是否存在 { cout << "can't open this file!" << endl; } fnew.count_chars = C_chars(f, fnew, wnew); fnew.count_words = C_words(f, wnew, log); //計算單詞數(插入哈希節點) rank_word(log, wnew); //詞頻排名 //大寫「ABCD」和小寫「abcd」應被當作同一詞彙統計 Assert::AreEqual(wnew.word_rank[1], string("abcd")); Assert::AreEqual(wnew.count_rank[1], 2); } }; }
之前就有了解VS有自帶的代碼覆蓋率檢測,此次做業實現時發現代碼覆蓋率結果須要VS企業版 纔有提供,最後查閱了這篇博客。給VS2017裝了一個小插件OpenCppCoverage才能夠運行。
這裏簡單給出一個小教程 (查閱不少資料都沒有很好的使用方法)
代碼覆蓋率結果以下圖所示
除了圖示的WordCount.cpp覆蓋率不高之外,其他的代碼覆蓋率都十分高,總覆蓋率爲 91% ,仔細看函數內部結構發現,該cpp中存在多處異常檢測與提示,再加上本次測試給定的參數正確,因此這也是代碼覆蓋率不高的緣由。(測試正常輸出)
對應單元測試以下
TEST_METHOD(Exception_input) { File fnew; int flag_input_exception = 0; strcpy_s(fnew.file_name, "../UnitTest1/test/test11.txt");//輸入文件名異常 ifstream f; if (!f.is_open()) { flag_input_exception = 1; //輸入異常標誌 } Assert::AreEqual(flag_input_exception, 1); } TEST_METHOD(Exception_output) { File fnew; int flag_output_exception = 0; strcpy_s(fnew.file_name, "../UnitTest1/test/ ");//輸出文件異常 ofstream fo; fo.open(fnew.file_name , ios::out); //輸出文件 if (!fo.is_open()) //輸出文件合法性檢查 { flag_output_exception = 1; //輸出異常標誌 } Assert::AreEqual(flag_output_exception, 1); }
最後在跑了幾回由cbattle同窗提供的測試數據,發現運行時間相差不大,這裏還要感謝個人一個沒有參加軟工實踐課程的舍友,發現了輸出界面的差別—— system("pause") 致使了命令行窗口沒有正確中止。我認爲這也就是本次TLE的緣由。
因爲原先的實現方式是正則表達式匹配,因此也把整個文本讀入來進行全文匹配單詞,可是發現這樣的方式在實現底層匹配時候不是特別方便。因而改進成vector_string 的形式來解決,具體實現方式以下流程圖所示: .
system("pause")
之類的致使等待時間過長。[2] http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html