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

本次做業隊友
031602502: 結對隊友博客
同名倉庫Github
本次做業博客連接css

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

簡要目錄:

  • Step1 · 結對信息

    成員信息html

    具體分工java

    代碼規範python

    PSP表格git

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

    爬蟲使用github

    代碼組織與內部實現設計(類圖)正則表達式

    關鍵算法及流程圖算法

    關鍵代碼解釋數組

  • Step3 · 附加題設計與展現

    設計創意獨到之處緩存

    實現思路

    實現成果展現

  • Step4 · 測試與優化

    性能分析與改進

    單元測試

  • Step5 · 結對過程

    代碼簽入記錄

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

    評價隊友

    學習進度條

  • Step6 · 附件

Step1 · 結對信息:


成員信息:

蔡宇航,031602501: 博客Github

陳柏濤,031602502: 博客Github

具體分工:

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

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

    代碼複審、測試(單元測試)

  • 陳柏濤:

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

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

代碼規範:

  • 咱們一塊兒制定了代碼規範
    結對制定的代碼規範見:
    博客戳這裏!

  • 敲重點!

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

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實現設計思路介紹(如下介紹基本功能的爬蟲實現過程):
  • 知識點:
    URL(Uniform Resource Location),即Web上的文件提供的惟一地址,能夠叫作統一資源定位器。
  • 使用的函數方法:

1.使用java.net.URL類的構造方法,爲該文件建立一個URL對象

2.URL類中定義的openStream()方法來打開輸入流和用輸入流

3.BufferedReader 是緩衝字符輸入流。它繼承於Reader。
BufferedReader 的做用是爲其餘字符輸入流添加一些緩衝功能。每次從中讀取一部分數據到緩衝中進行操做。(將網頁html存入,每次讀取一行進行正則匹配獲取全部論文url並存於list中)

4.同理使用BufferedWriter來輔助文件寫入

5.使用java.util.regex包進行正則匹配。其中Pattern類下的compile()方法編譯正則表達式,返回正則表達式被編譯後的pattern。
而其中的Matcher類沒有提供什麼靜態方法,經過調用 Pattern 對象的 matcher 方法來得到一個 Matcher 對象。而後使用Matcher對象的.find()函數來判斷是否存在匹配。利用group(i)來返回匹配項(i從1開始)

  • 獲取網頁html源碼
URL url=new URL("http://openaccess.thecvf.com/CVPR2018.py");    //創建http鏈接並返回鏈接對象
        BufferedReader bufr=new BufferedReader(new InputStreamReader(url.openStream()));
  • 匹配論文url的正則表達式(利用標籤惟一肯定):
String mail_regex = "<dt class=\"ptitle\"><br><a href=\"(.*?)\">";
  1. 傳入網站URL,正則表達式匹配全部論文的URL存下來
  2. 遍歷全部論文的URL(注意要加上http://openaccess.thecvf.com/),分別進行網頁讀取,正則表達式匹配各個論文的標題和摘要並按題目要求格式寫入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論文列表)

  • 實現了附加功能需求:

一、爬取了各個論文的屬性以及做者和做者工做單位用於附加功能界面的實現(按論文標題進行了同一論文的合併)。利用了網上github概括整合的論文屬性進行爬取 https://github.com/amusi/daily-paper-computer-vision/blob/master/2018/cvpr2018-paper-list.csv
二、爬取了CVPR2018論文PDF連接和論文網址

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

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

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

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

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

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

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

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

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

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

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

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

    一、title 論文標題

    二、abstract 論文摘要

    三、將論文抽象成一個結構,保證了可拓展性,還能夠加入如論文做者、論文網站、論文PDF鏈接等屬性,還能夠對論文屬性進行枚舉。

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

    一、getCharNumber() 獲取字符個數

    二、getWordNumber() 獲取單詞個數

    三、getLineNumber() 獲取獲取行數

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

    五、_getAllPhrase() 獲取全部詞組,用於調試

    六、isLetter() 判斷給定字符是否爲字母

    七、isNumber() 判斷給定字符是否爲數字

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

    九、calcAll() 對全部文本內容fileContent執行統計功能

關鍵算法及流程圖:

  • 統計單詞算法

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

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

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

    四、重複步驟3直至處理完整個字符串。

  • 獲取最高頻的n個詞組

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

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

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

關鍵代碼解釋

  • 短語統計

    一、以「分隔符-單詞-分隔符-單詞-分隔符-單詞」的形式壓入短語緩存隊列。

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

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個短語

    一、爲確保正確,n應取需求個數n和不一樣短語個數phraseMap.size()的最小值。

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

    三、取完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+ python

實現成果展現:

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

  • 熱詞圖譜效果圖(部分):

  • 前10熱詞(包括長度爲2詞組)

  • 前50熱詞(包括長度爲2詞組)

  • 使用python生成熱詞圖譜(源碼以下)
from wordcloud import WordCloud
import matplotlib.pyplot as plt

f = open(u'Spider_result.txt','r',encoding='utf-8').read()

#修改基本功能需求文件內容格式
content = ""
i = 0
while(i<len(f)):
    if f[i]>='0'and f[i]<='9':
        while(f[i]!='\n'):
            i+=1
        i+=1
        while(f[i]!=' '):
            i+=1
        i+=1
        title=""
        while(f[i]!='\n'):
            title+=f[i]
            i+=1
        i+=1
        while(f[i]!=' '):
            i+=1
        i+=1
        abstract=""
        while(i < len(f)):
            if f[i]=='\n':
                break
            else:
                abstract += f[i]
            i+=1
        i+=3
        content+=title + '\n' +  abstract + '\n'

wordcloud = WordCloud(
        background_color="white", #設置背景爲白色,默認爲黑色
        width=1500,              #設置圖片的寬度
        height=960,              #設置圖片的高度
        max_words=50,            #設置顯示的熱詞數
        margin=10               #設置圖片的邊緣
        ).generate(content)
# 繪製圖片
plt.imshow(wordcloud)
# 消除座標軸
plt.axis("off")
# 展現圖片
plt.show()
# 保存圖片
wordcloud.to_file('hot words2.png')

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() 正確輸出頻率前十單詞(帶權重)
Top2Phrase_3(nw)_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 · 結對過程


代碼簽入記錄:

  • 蔡宇航:
  • 陳柏濤:

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

  • 代碼模塊異常:

    一、在實現爬蟲的過程當中遇到了亂碼的狀況,經過轉換編碼爲UTF-8解決了這一點

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

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

  • 結對困難

    一、總是以爲國慶的時間很長,事情留到國慶作,可是假期的效率過低。

    二、因爲詞頻統計原先是我的做業,雙方原先代碼風格規範不一致,加上功能模塊較少,協做起來困難,因此在思路共同討論的狀況下分工合做。

評價隊友:

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

    值得學習的地方:

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

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

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

    須要改進的地方:

        睡眠時間要加長

        

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

    值得學習的地方:

        一、強烈的求知慾,刨根問底。

        二、學習能力強,學東西快。

        三、有良好的編碼規範。

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

    須要改進的地方:

        不要熬夜

        

學習進度條:

蔡宇航的學習進度條
陳柏濤的學習進度條

Step6 · 附件

相關文章
相關標籤/搜索