1. 程式人生 > >軟工實踐第三次作業-結對項目2

軟工實踐第三次作業-結對項目2

新技術 design 三次 [1] ade nac hash 頻率 直線

軟工實踐第三次作業-結對項目2

簡要目錄:

  • Step1 · 結對信息

????成員信息

????具體分工

????代碼規範

????PSP表格

  • Step2 · 解題思路與設計實現

????爬蟲使用

????代碼組織與內部實現設計(類圖)

????關鍵算法及流程圖

????關鍵代碼解釋

  • Step3 · 附加題設計與展示

????設計創意獨到之處

????實現思路

????實現成果展示

  • Step4 · 測試與優化

????性能分析與改進

????單元測試

  • Step5 · 結對過程

????代碼簽入記錄

????遇到的代碼模塊異常或結對困難及解決方法

????評價隊友

????學習進度條

  • Step6 · 附件

Step1 · 結對信息:


成員信息:

蔡宇航,031602501: 博客,Github

陳柏濤,031602502: 博客,Github

具體分工:

  • 說明:在各個方面設計的思路共同討論的前提下大概分工為:
  • 蔡宇航:

????爬蟲的實現(爬取所需要的資源)

????代碼復審、測試(單元測試)

  • 陳柏濤:

????界面的實現(與原型相仿的交互性界面)

????基本功能(詞頻統計)代碼的實現

代碼規範:

結對制定的代碼規範見:
link

  • 敲重點!

多交流、多溝通是最好的規範。

PSP表格:

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 30 30
· Estimate · 估計這個任務需要多少時間 30 30
Development 開發 2630 3000
· Analysis · 需求分析 (包括學習新技術) 360 480
· Design Spec · 生成設計文檔 120 120
· Design Review · 設計復審 30 30
· Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 120 90
· Design · 具體設計 120 120
· Coding · 具體編碼 1200 1500
· Code Review · 代碼復審 200 120
· Test · 測試(自我測試,修改代碼,提交修改) 480 540
Reporting 報告 100 130
· Test Repor · 測試報告 30 30
· Size Measurement · 計算工作量 10 10
· Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 60 90
· 合計 2760 3160

Step2 · 解題思路與設計實現


解題思路:

  • 大致的解題思路在下方關鍵算法已介紹,這裏來說說一些數據結構的選擇的精巧之處:

????map和hash_map的取舍

爬蟲使用:

  • 使用了java實現爬蟲,用python再次爬取數據,將二者爬取結果進行文本對比,以確保結果正確性。(python僅用來驗證)
  • java實現設計思路介紹:
  1. 傳入網站URL,正則表達式匹配所有論文的URL存下來
  2. 遍歷所有論文的URL,分別進行網頁讀取,正則表達式匹配各個論文的標題和摘要並按題目要求格式寫入txt中
  3. 註意需用UTF-8格式(論文有各種符號如果用默認編碼格式將產生亂碼)
  4. 詳細源碼見附錄
            String head = "http://openaccess.thecvf.com/"; 
            List<String> list=getMailsByWeb();
            File f = new File("./Spider_result.txt");      
            if (!f.exists())
            {       
                f.createNewFile();      
            }      
            OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");      
            BufferedWriter writer=new BufferedWriter(write); 
            int num = 0;    
            int flag = 0;
            for(String mail:list)
            {
                mail = head + mail;
                URL url=new URL(mail);
                System.out.println(mail);
                System.out.println("\n");       
                BufferedReader bufred=new BufferedReader(new InputStreamReader(url.openStream(),"UTF-8"));              
                String title_regex = "<div id=\"papertitle\">\\n(.*?)</div>";
                String abstract_regex = "<div id=\"abstract\" >\\n(.*?)</div>";
                Pattern p = Pattern.compile(title_regex);
                Pattern q = Pattern.compile(abstract_regex);
                String line = null;
                String content = null;
                while((line=bufred.readLine())!=null)
                {
                    content = content + line + "\n";
                }
                Matcher w = p.matcher(content);
                Matcher n = q.matcher(content); 
                if(w.find()&&n.find())
                {
                    if(flag == 1)
                    {
                        writer.write("\n\n\n"); 
                    }       
                    flag = 1;                   
                    writer.write(String.valueOf(num));
                    num++;
                    writer.write("\nTitle: "); 
                    writer.write(w.group(1)); 
                    writer.write("\nAbstract: "); 
                    writer.write(n.group(1)); 
                }
            }
            writer.close();
  • 實現了題目的基本需求(爬取CVPR2018論文列表)

  • 實現了附加功能需求:

1、爬取了各個論文的屬性以及作者和作者工作單位用於附加功能界面的實現(按論文標題進行了同一論文的合並)。利用了網上github歸納整合的論文屬性進行爬取 link
2、爬取了CVPR2018論文PDF鏈接和論文網址

代碼組織與內部實現設計(類圖):

說明:
  • ArgProcessing類封裝了命令行參數處理的方法。傳入argc、argv,通過get字段取出對應參數數值。

????1、ArgProcessing() 構造函數:對參數進行處理

????2、getArgiInFilePath() 獲取-i後的參數:輸入文件路徑

????3、getArgoOutFilePath() 獲取-o後的參數:輸出文件路徑

????4、getArgwUseTitleWeight() 獲取-w參數:是否使用標題10倍權重

????5、getArgmPhraseLen() 獲取-m參數:短語長度

????6、getArgnTopNum() 獲取-n參數:結果所需單詞數

  • FileIO類封裝了用於特定格式文件讀寫的方法

????1、readFile() 按照指定格式讀取文件

????2、writeResult() 按照指定格式寫文件

  • Paper結構對應於論文的結構

????1、title 論文標題

????2、abstract 論文摘要

????3、將論文抽象成一個結構,保證了可拓展性,還可以加入如論文作者、論文網站、論文PDF連接等屬性,還可以對論文屬性進行枚舉。

  • Statistics結構對應於論文的結構

????1、getCharNumber() 獲取字符個數

????2、getWordNumber() 獲取單詞個數

????3、getLineNumber() 獲取獲取行數

????4、getTopPhrase() 獲取最高頻的n個詞組

????5、_getAllPhrase() 獲取所有詞組,用於調試

????6、isLetter() 判斷給定字符是否為字母

????7、isNumber() 判斷給定字符是否為數字

????8、calcString() 對給定字符串執行統計功能

????9、calcAll() 對所有文本內容fileContent執行統計功能

技術分享圖片

關鍵算法及流程圖:

  • 統計單詞算法

????1、定義單詞緩存wordBuf、分隔符緩存separatorBuf、詞組緩存隊列phraseBuf,初始化為空。

????2、對給定字符串進行遍歷,遍歷過程中記錄單詞緩存、分隔符緩存。

????3、遇到分隔符:若單詞緩存中不是單詞,清空分隔符緩存和詞組緩存隊列;若單詞緩存中是單詞,則將分隔符壓入詞組緩存隊列,將單詞緩存壓入詞組緩存隊列。若詞組緩存隊列長度達到指定的詞組長度的2倍(因為壓入的是分隔符和單詞),彈出詞組緩存隊列首個分隔符,遍歷詞組緩存隊列取出詞組,彈出詞組緩存隊列首個單詞。

????4、重復步驟3直至處理完整個字符串。

技術分享圖片

  • 獲取最高頻的n個詞組

????1、對按照以詞組————次數存的map進行n次遍歷

????2、每次遍歷,將次數最多的詞組的叠代器取出。通過叠代器將詞組次數置為相反數(設為負數,下次尋找最大值排除在外)。將取出的叠代器加入結果數組。

????3、取完n個叠代器後,對結果數組進行遍歷,將詞組次數置為相反數(恢復正數)。完成。

技術分享圖片

關鍵代碼解釋

  • 短語統計

????1、以“分隔符-單詞-分隔符-單詞-分隔符-單詞”的形式壓入短語緩存隊列。

????2、取短語時,先冒出短語緩存隊列的首個分隔符,然後遍歷隊列取出短語,最後冒出短語緩存隊列的首個單詞。

            if (wordBuf.size() >= 4 && isLetter(wordBuf[0]) && isLetter(wordBuf[1]) && isLetter(wordBuf[2]) && isLetter(wordBuf[3]))
            {
                m_wordNum++;
                phraseBuf.push_back(separatorBuf);      // 將分隔符壓入詞組緩存
                phraseBuf.push_back(wordBuf);           // 將單詞壓入詞組緩存
                separatorBuf.clear();
                wordBuf.clear();
                if (int(phraseBuf.size()) == m_phraseLen * 2)//壓入時,單詞、分隔符成對壓入,所以是2倍
                {
                    phraseBuf.pop_front();              // 彈出分隔符。
                    string thisPhrase;
                    for (list<string>::iterator it = phraseBuf.begin(); it != phraseBuf.end(); it++)
                    {                                   // 遍歷詞組緩存隊列,取出詞組
                        thisPhrase += *it;
                    }
                    m_phraseMap[thisPhrase] += weight;  // 在map中進行個數統計
                    phraseBuf.pop_front();              // 彈出單詞
                }
            }
  • 取最高頻的n個短語

????1、為確保正確,n應取需求個數n和不同短語個數phraseMap.size()的最小值。

????2、對phraseMap遍歷n遍,每次遍歷取出最高頻次對應的iterator,加入結果,並通過iterator將詞頻置為相反數(確保下一次遍歷時不考慮它)。

????3、取完n個iterator後,應對取出的iterator再次遍歷,將詞頻置為相反數,恢復詞頻為正數。

    topNum = min(topNum, int(m_phraseMap.size()));
    for (int i = 0; i < topNum; i++)
    {
        unordered_map<string, int>::iterator maxit = m_phraseMap.begin();
        for (unordered_map<string, int>::iterator it = m_phraseMap.begin(); it != m_phraseMap.end(); it++)
        {
            if (it->second > maxit->second || it->second == maxit->second && it->first < maxit->first)
            {
                maxit = it;
            }
        }
        m_topPhrase.push_back(maxit);
        maxit->second = -maxit->second;
    }
    for (unsigned int i = 0; i < m_topPhrase.size(); i++)
    {
        m_topPhrase[i]->second = -m_topPhrase[i]->second;
    }

Step3 · 附加題設計與展示


設計創意獨到之處

  • 實現了交互界面將創新想法需求與界面相結合(基本與上回作業的設計原型一致)
  • 用java從網站綜合爬取論文的除題目、摘要外其他信息
  • 對數據的圖形可視化做了一些努力,將對通過java爬取的數據進行處理,依據處理的結果繪制統計圖
  • 在界面的基礎上實現了(一些右鍵功能的加入(方便操作))
  1. 每日推薦:進行隨機推薦(支持刷新以更換推薦),對於推薦的論文可以進行收藏,以及打開其PDF鏈接
  2. 論文搜索:按照作者和標題進行論文的檢索
  3. 流行趨勢:對所有論文的熱詞進行統計並畫出熱詞直方圖(對爬取結果)
  4. 人物風采:展示了論文重要Speakers的簡介
  5. 我的收藏:收藏功能的實現以及對已收藏論文的批量管理。
  6. 軟件設置:實現了各種主題皮膚的切換(更加滿足不同用戶的個性化需求)

實現思路:

  • 公用的是左邊的圖片和若幹個按鈕,采用垂直線性布局,右邊一個QWidget,用來放子界面。整個頁面采用水平線性布。對應的子界面構造出來後,都作為右邊空Widget的子對象放在右邊的Widget中。
  • 論文列表采用QListWidget,添加論文項目用addItem添加一行。
    。右鍵菜單的實現用到了QMenu,使用addAction對menu加入指定的action。
  • 統計圖的實現,是在QChartView中用QChart來繪制的。(數據是使用爬蟲爬下來的。ps:真實數據)
  • 主題的切換,采用css和qss樣式表實現。
  • 事件的觸發,用Qt的信號和槽機制。
  • 搜索功能采用關鍵詞匹配

實現工具:QT5 + MSVC 2017

實現成果展示:

  • 說明:成果exe和代碼見附件

1_每日推薦界面

技術分享圖片

2_論文搜索界面

技術分享圖片

2_論文搜索界面_搜索功能

技術分享圖片

3_流行趨勢_十大熱詞排名統計圖

技術分享圖片

4_人物界面

技術分享圖片

5_我的收藏界面

技術分享圖片

6_設置界面

技術分享圖片

6_設置界面_更改頭像

技術分享圖片

6_設置界面_更換主題

技術分享圖片

7_暗黑橙主題_論文搜索界面

技術分享圖片

7_暗黑橙主題_每日推薦界面

技術分享圖片

7_清新藍主題_論文搜索界面

技術分享圖片

7_清新藍主題_每日推薦界面

技術分享圖片

7_尊貴金主題_論文搜索界面

技術分享圖片

Step4 · 測試與優化


性能分析與改進:

  • 說明:Debug x86編譯,phraseLen = 1 topNum = 10

  • 起初使用map實現

技術分享圖片

  • 瓶頸:
    技術分享圖片

-改進:用 unordered map 代替 map

技術分享圖片

  • 瓶頸:

技術分享圖片

技術分享圖片

警告消除:

技術分享圖片

單元測試:

  • 運行說明:若要運行單元測試請以x86形式運行
  • 樣例說明:(前四個對應四種異常處理,後六個為對接口函數的測試)
傳入(或輸出)文件名 測試文件 測試函數 期待輸出
aaa.txt(不存在的文件名) FileIO.h FileIO::readFile() 輸入錯誤的文件名(異常處理)
empty.txt(傳入)\//\//(輸出) FileIO.h File::writeResult() 輸出文件名錯誤(異常處理)
ArgProcessing.h ArgProcessing:ArgProcessing() 主函數參數過多(異常處理)
ArgProcessing.h ArgProcessing::ArgProcessing() 錯誤命令行參數(異常處理)
lines(nw)_test.txt Statistics.h Statistics::getLineNumber() 正確統計文本的行數(不帶權重)
words(nw)_test.txt Statistics.h Statistics::getWordNumber() 正確統計文本中的單詞數(不帶權重)
char(nw)_test.txt Statistics.h Statistics::getCharNumber() 正確統計字符數(不帶權重)
TopPhrase_3(nw)_test.txt Statistics.h Statistics::getTopPhrase() 正確輸出頻率前十的詞組(不帶權重)
Top9Phrase_1(w)_test.txt Statistics.h Statistics::getTopPhrase() 正確輸出頻率前十單詞(帶權重)
max_fre_test.txt Statistics.h Statistics::getTopPhrase() 正確輸出頻率前n的詞組(不帶權重)

部分代碼展示:

  • 文件讀寫錯誤(異常處理)
TEST_CLASS(UnitTestFor_FileIO)
    {
    public:

        TEST_METHOD(TestFor_readFile)
        {
            auto fun = [this]
            {
                const char* inputfile = "aaa.txt";
                const char* outputfile = "result_test.txt";
                vector<Paper> fileContent;
                bool showInConsole = false;
                FileIO::readFile(fileContent, inputfile, showInConsole);
            };
            Assert::ExpectException<const char*>(fun);
        }
        TEST_METHOD(TestFor_writeFile)
        {
            auto fun = [this]
            {
                const char* inputfile = "empty.txt";
                const char* outputfile = "\\//\\//";
                vector<Paper> fileContent;
                int phraseLen = 1;
                bool useTitleWeight = false;
                bool showInConsole = false;
                int topNum = 10;
                Statistics st(fileContent, useTitleWeight, phraseLen);
                vector<unordered_map<string, int>::iterator> &topPhrase = st.getTopPhrase(topNum);
                FileIO::writeResult(1, 1, 1, topPhrase, outputfile, showInConsole);
            };
            Assert::ExpectException<const char*>(fun);
        }
    };
  • 參數判斷(異常處理)
    TEST_CLASS(UnitTestFor_ArgProcessing)
    {
    public:
        TEST_METHOD(TestFor_ArgProcessingOvermuch)
        {
            auto fun = [this]
            {
                char *argv[4] = { "WordCount.exe","-i","input.txt","-n" };
                int argc = 3;
                ArgProcessing ap(argc, argv);
            };
            Assert::ExpectException<const char*>(fun);
        }
        TEST_METHOD(TestFor_ArgProcessingWrong)
        {
            auto fun = [this]
            {
                char *argv[13] = { "WordCount.exe","-i","input.txt","-m","3","-n","3","-w","1","-o","output.txt","yeah" };
                int argc = 12;
                ArgProcessing ap(argc, argv);
            };
            Assert::ExpectException<const char*>(fun);
        }
    };
  • 正確輸出頻率前十的詞組(不帶權重)
        TEST_METHOD(TestFor_getTopPhrase_1)
        {
            const char* inputfile = "TopPhrase_3(nw)_test.txt";
            vector<Paper> fileContent;
            int topnum = 10;
            int phraseLen = 3;
            bool useTitleWeight = false;
            bool showInConsole = false;
            FileIO::readFile(fileContent, inputfile, showInConsole);
            Statistics st(fileContent, useTitleWeight, phraseLen);
            string answer[10] ={
            "abcd_abcd_abcd","abcd_abcd.then",
            "else_todo_abcd","then_else_todo","todo_abcd_abcd",
            "abcd.abcd.abcd","abcd.then,else","abcd.then_else",
            "abcd_abcd.abcd","then,else;todo"};
            int frequence[10] = {9,6,6,6,6,3,3,3,3,3};
            vector<unordered_map<string, int>::iterator> &tem = st.getTopPhrase(topnum);
            //FILE *fp = NULL;
            //fopen_s(&fp, "asd.txt", "w");
            for (int i = 0; i < 10; i++)
            {
                /*fprintf(fp, "%d\n", tem[i]->second);*/
                Assert::IsTrue(tem[i]->first == answer[i] && tem[i]->second == frequence[i]);
            }
        }

運行結果:

技術分享圖片

技術分享圖片

代碼覆蓋率:

  • 說明:使用了一個樣例來測試代碼覆蓋率(代碼覆蓋率不高的原因是一些測試用的函數和異常處理的模塊調用的少)(註釋掉測試用函數,所有單元測試的代碼覆蓋率應該是接近100%的)

技術分享圖片

技術分享圖片

技術分享圖片

Step5 · 結對過程


代碼簽入記錄:

遇到的代碼模塊異常或結對困難及解決方法:

  • 代碼模塊異常:

????1、在實現爬蟲的過程中遇到了亂碼的情況,通過轉換編碼為UTF-8解決了這一點

????2、在實現爬蟲的過程出現了爬取過程中get請求某個論文url時超時導致爬蟲卡在那一段,後來利用了設置超時時間來捕獲異常超時處理,異常處理為重新加載(return)

????3、詞組詞頻統計時對於單詞之間分隔符忽略了,導致最後輸出的詞組僅已空格分隔,後來使用了一個詞組緩沖隊列,每次壓入單詞前先壓入分隔符緩存,取出單詞前先冒掉首個分隔符緩存,再遍歷隊列取出詞組,冒掉首個單詞。

  • 結對困難

????1、老是覺得國慶的時間很長,事情留到國慶做,但是假期的效率太低。

????2、由於詞頻統計原先是個人作業,雙方原先代碼風格規範不一致,加上功能模塊較少,協作起來困難,所以在思路共同討論的情況下分工合作。

評價隊友:

  • 評價陳柏濤同學(蔡宇航)

????值得學習的地方:

????????1、代碼能力以及代碼的規範程度

????????2、時間觀念強,很早的完成任務

????????3、學習能力強,新的東西學習快

????需要改進的地方:

????????睡眠時間要加長

????????

  • 評價蔡宇航同學(陳柏濤)

????值得學習的地方:

????????1、強烈的求知欲,刨根問底。

????????2、學習能力強,學東西快。

????????3、有良好的編碼規範。

????????4、思路靈活,富有創造性。

????需要改進的地方:

????????不要熬夜

????????

學習進度條:

陳柏濤的學習進度條

第N周 新增代碼(行) 累計代碼(行) 本周學習耗時(小時) 累計學習耗時(小時) 重要成長
1 200 200 6 6 學習PyQt5的基本使用
2
3
4

Step6 · 附件


軟工實踐第三次作業-結對項目2