1. 程式人生 > >結對第2次作業——WordCount進階需求

結對第2次作業——WordCount進階需求

red lib txt mage 內部實現 com 表格 unicode odin

結對第2次作業——WordCount進階需求

博客鏈接

081600107 傅濱: 博客地址: https://www.cnblogs.com/fblogy/p/9766315.html
031602248 鄭智文:博客地址: https://www.cnblogs.com/Zzwena/p/9768881.html

Github地址

https://github.com/fblogy/PairProject-C

具體分工

主要由我實現了WordCount代碼部分,隊友實現了爬蟲部分

PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 50 55
? Estimate ? 估計這個任務需要多少時間 15 20
Development 開發 600 520
? Analysis ? 需求分析 (包括學習新技術) 80 50
? Design Spec ? 生成設計文檔 20 10
? Design Review ? 設計復審 20 10
? Coding Standard ? 代碼規範 (為目前的開發制定合適的規範) 20 10
? Design ? 具體設計 50 40
? Coding ? 具體編碼 250 230
? Code Review ? 代碼復審 60 50
? Test ? 測試(自我測試,修改代碼,提交修改) 100 120
Reporting 報告 70 60
? Test Repor ? 測試報告 30 25
? Size Measurement ? 計算工作量 10 10
? Postmortem & Process Improvement Plan ? 事後總結, 並提出過程改進計劃 30 25
合計 720 635

解題思路描述與設計實現

  • 爬蟲使用
    用pycharm爬取網站的論文列表。
    先在CVPR2018官網上找到論文列表檢查元素,發現每篇論文都有一個超鏈接
    例如:content_cvpr_2018/html/Misra_Learning_by_Asking_CVPR_2018_paper.html
    點開論文後,可以看到網址:
    http://openaccess.thecvf.com/content_cvpr_2018/html/Misra_Learning_by_Asking_CVPR_2018_paper.html
    只是在前面加了:http://openaccess.thecvf.com/
    所以我們只需要爬去每篇論文的超鏈接再加上頭部分就是每篇論文的網址。
    打開每篇論文的鏈接,在每篇論文中右鍵標題和摘要,發現都有一個div id
    標題:papertitle
    技術分享圖片
    摘要:abstract
    技術分享圖片
    中間部分就是相應的內容,我們爬去到txt文件中就行了。

代碼實現

#獲取論文列表的超鏈接
resp=urllib.request.urlopen(url)
    html=resp.read()
    html=html.decode()
    links=re.findall(r‘content_cvpr_2018/html/.*.html‘,html)  #所有論文網址尾部分
 # 依次打開每篇論文的網址
    for a in links:   
        resp = urllib.request.urlopen(head+a)      #論文網址
        html = resp.read()
        html = html.decode()
        #獲取title
        t1 = re.search(r‘\"papertitle\">‘,html)
        t1 = html[t1.end()+1:]
        t2 = re.search(r‘</div>‘,t1)
        title = t1[:t2.start()]
        print(i, file=data)
        print("Title: ", title, file=data)
        #獲取abstract
        a1 = re.search(r‘\"abstract\"‘, html)
        a1 = html[a1.end()+3:]
        a2 = re.search(r‘</div>‘, a1)
        abstract = a1[:a2.start()]
        print("Abstract: ", abstract,end="\n\n\n",file=data)
        i=i+1
    data.close()
  • 代碼組織與內部實現設計(類圖)

技術分享圖片

  • 算法的關鍵與關鍵實現部分流程圖

技術分享圖片

關鍵代碼解釋

int solve(int l, int r, int w, int m) {
    string s;  int ch, now = 0, n1 = 0, cnt = 0;
    string t;
    rep(i, l, r) {
        ch = a[i];
        if (ch >= ‘A‘ && ch <= ‘Z‘) ch -= ‘A‘ - ‘a‘; //大寫字母轉小寫
        if (now == -2) { if (!checkword(ch) && !checknumber(ch)) now = 0; continue; }
        // now == -2 表示現在處於非法單詞中,只有遇到分隔符才能重新統計新一段單詞,不然還是處於非法單詞
        if (checkword(ch)) { // 遇到字母自動機轉移
            now++; s += ch;
            if (now == 1) fg[n1] = t, t = "";
            continue; 
        }
        if (checknumber(ch)) { // 遇到數字自動機轉移
            if (now >= 4) now++, s += ch;
            else now = -2, s = "", t = "", ADD(n1, word, fg, w, m, cnt);
            continue;
        } // 遇到分隔符自動機轉移
        if (now >= 4) word[++n1] = s;
        else if (now != 0) ADD(n1, word, fg, w, m, cnt); // 對一段合法單詞去統計詞組
        t += ch;s = ""; now = 0;
    }
    if (now >= 4) {
        word[++n1] = s, s = "";
        ADD(n1, word, fg, w, m, cnt);
    }
    return cnt;
}

這部分代碼是在處理出一行是屬於Title還是Abstract後去統計詞組,由於一個詞組中不能有不合法單詞,相當於不合法的單詞把一些合法的單詞隔成了一段一段,我就是先把一段連續合法單詞保存下來,再把這一段中的詞組添加到unordered_map中。

性能分析與改進

技術分享圖片
技術分享圖片

可以看出,時間消耗主要是在CountWord和CountFrequentWord部分,且主要是在其中調用unordered_map使字符串和數量映射在一起。

改進思路

其實CountWord部分不需要映射,因為只需要知道總單詞數量即可,但我為了代碼復用,可以把單詞看成是長度為1的詞組,所以一起用了unordered_map,如果性能要求很高的話可以在這進行改進。
對於映射的數據結構我覺得unordered_map效率已經比較高了,比map會快很多,而且代碼難度比較低,我嘗試用過字典樹,但是由於這次是詞組,所以長度會比較長,而且中間會出現非數字字母的分隔符,所以效率不高,總體來講映射的數據結構這部分我覺得不需要改進了。

單元測試

技術分享圖片

        TEST_METHOD(TestMethod2)
        {
            char name[100];
            strcpy_s(name, "../UnitTest1/test/test2.txt");      // 測試統計字符數
            int count_chars = CountChar(name);
            Assert::AreEqual(count_chars, 74);
        }
        TEST_METHOD(TestMethod4)                                // 測試統計單詞數
        {
            char name[100];
            strcpy_s(name, "../UnitTest1/test/test2.txt");
            pii count_words = CountWord(name);
            Assert::AreEqual(count_words.fi, 9);
        }
        TEST_METHOD(TestMethod6)                                // 測試統計有效行數函數
        {
            char name[100];
            strcpy_s(name, "../UnitTest1/test/test2.txt");
            pii count_words = CountWord(name);
            Assert::AreEqual(count_words.se, 2);
        }
        
        // 9 和 10 用來測試數據較大時的效率和不同參數時詞組頻率統計
        TEST_METHOD(TestMethod9)
        {
            char name[100];
            strcpy_s(name, "../UnitTest1/test/test3.txt");

            int count_chars = CountChar(name);
            pii tmp = CountWord(name);
            int count_words = tmp.fi;
            int count_lines = tmp.se;
            vector<pair<string, int> > word_rank = CountFrequentWord(name, 10, 3);
            Assert::AreEqual(count_words, 117998);
            Assert::AreEqual(count_chars, 1196799);
            Assert::AreEqual(count_lines, 1958);
            Assert::AreEqual(word_rank[0].fi, string("convolutional neural networks"));
            Assert::AreEqual(word_rank[0].se, 196);
            Assert::AreEqual(word_rank[1].fi, string("generative adversarial networks"));
            Assert::AreEqual(word_rank[1].se, 177);
            Assert::AreEqual(word_rank[2].fi, string("convolutional neural network"));
            Assert::AreEqual(word_rank[2].se, 159);
        }
        TEST_METHOD(TestMethod10)
        {
            char name[100];
            strcpy_s(name, "../UnitTest1/test/test3.txt");

            int count_chars = CountChar(name);
            pii tmp = CountWord(name);
            int count_words = tmp.fi;
            int count_lines = tmp.se;
            vector<pair<string, int> > word_rank = CountFrequentWord(name, 10, 10);
            Assert::AreEqual(count_words, 117998);
            Assert::AreEqual(count_chars, 1196799);
            Assert::AreEqual(count_lines, 1958);
            Assert::AreEqual(word_rank[0].fi, string("partially shared multi-task convolutional neural network with local constraint"));
            Assert::AreEqual(word_rank[0].se, 11);
            Assert::AreEqual(word_rank[1].fi, string("beyond holistic object recognition: enriching image understanding with part states"));
            Assert::AreEqual(word_rank[1].se, 10);
            Assert::AreEqual(word_rank[2].fi, string("blazingly fast video object segmentation with pixel-wise metric learning"));
            Assert::AreEqual(word_rank[2].se, 10);
        }

我構造的單元測試1-8每兩個測試一個功能:統計字符,統計單詞,統計有效行數,統計詞組頻率,最後9和10用來測試總的爬下來的數據,同時測試效率和不同指令的功能。

Github的代碼簽入記錄

技術分享圖片
技術分享圖片
技術分享圖片

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

WordCount部分

  • 問題描述
    一開始的時候我打算用字典樹去存儲得到的字符串,但是再指令m比較大的時候,每個詞組會比較長,導致效率比較低,而且有時還會runtime error,應該是數組訪問越界。

  • 解決方案
    我把所有字符輸出來看後發現有些不在ASCII碼的範圍內,比如é這種,然後我就把它先過濾掉了,之後我嘗試了一下改用unordered_map,發現效率比較高,我就馬上改了,感覺之前用字典樹在這種情況下完全不如unordered_map。

  • 體會
    讓我對數據結構的選取更加有經驗了,而且知道了要更細致地考慮輸入數據,可能會有一些不合法的輸入要過濾掉,使程序的魯棒性更好。

隊友:爬蟲部分
1、`UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\xbb‘ in position 0: illegal multibyte sequence
解決方法:參考https://www.jb51.net/article/64816.html
data=open("D:\data.txt", ‘w+‘,encoding=‘utf-8‘) #打開輸出文件並改為標準輸出編碼
2、<urlopen error [WinError 10060] 由於連接方在一段時間後沒有正確答復或連接的主機沒有反應,連接嘗試失敗。>
解決方法:參考https://www.cnblogs.com/lliuye/p/8406267.html
沒采取措施,在第二次爬取數據時沒有出現此問題,順利完成。

評價隊友

評價鄭智文:
學習能力很強,分工明確,很快就將爬蟲部分學會,完成了數據的爬取!

學習進度條

日期 10月8日 10月9日 10月10日
完成任務 完成主函數和字符統計功能 完成詞頻統計功能 完成單元測試
學習爬蟲 完成爬蟲

結對第2次作業——WordCount進階需求