結對第二次—文獻摘要熱詞統計及進階需求

課程 軟件工程1916|W(福州大學)
做業要求 結對第二次—文獻摘要熱詞統計及進階需求
結對博客 221600426     221600401
Github基礎需求項目地址 221600426     221600401
Github進階需求項目地址 221600426     221600401
做業目標 實現一個可以對文本文件中的單詞的詞頻進行統計的控制檯程序,並能在基本需求實現的基礎上,經過爬取CVPR2018官網並進行頂會熱詞統計
具體分工 221600426負責主要代碼的編寫,221600401負責學習爬蟲和博客的撰寫

Github的代碼簽入記錄:


PSP表格

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

解題思路描述:即剛開始拿到題目後,如何思考,如何找資料的過程

    首先是語言的選擇,因爲221600426較常作LeetCode題,以及較常使用.net,因此開頭是打算使用C++來開發的,後來較詳細的分析了需求後發現對於此題,咱們可能會須要用到大量的正則表達式以及爬蟲,java有大量的類庫以及前人的經驗,因此最終選擇用java來實現。而後是爬蟲框架的選擇,goole了一下,搜到較多的jsoup實現爬蟲的相關信息,所以咱們就選擇了學習jsoup;分析程序要實現的基本功能,後確認至少須要有三個接口(字符計數,行計數,單詞計數),將其封裝成一個類不妨稱爲WordsHandler,最後針對每個功能進行編碼的實現,以及進行模塊測試,確保獨立模塊穩定,再進行拼接以及總體測試。基本功能實現後就能夠考慮進階需求,先在jsoup Cookbook(中文版)學習jsoup,而後邊學邊寫爬蟲,最後在基礎功能之上經過小修改便可實現進階需求。html

基礎需求設計實現過程:

  • 代碼如何組織
    共需兩個類,一個程序入口Main,一個文本處理WordsHandler。Main實例化一個WordsHandler,該實例在調用其接口charCnt,lineCnt,wordCnt進行字符,有效行數,單詞數統計,最後調用printInfo進行輸出。java

  • 單元測試的設計
    git

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

  • 算法的關鍵與關鍵實現部分流程圖
    基礎需求較爲簡單,硬要找到算稍微難點的,應該是wordCnt的實現。該接口接收一行字符串,首先用正則表達式"[^a-zA-Z0-9]"匹配非字母數字符號的位置,分割出全部的可能單詞(存在不符合題目定義"必須4個字母開頭"),而後把全部單詞轉爲小寫,並遍歷單詞數組,若是該詞匹配正則"[a-z]{4}[a-zA-Z0-9]*"則它就是題目定義的單詞,那麼就把單詞總數+1,並判斷單詞map中是否存在以該單詞爲key的二元組,若不存在則將單詞做爲key,並用1做爲value存入map,不然在對應key的位置將其value+1。
//單詞數統計
    public void wordCnt(String line) {
        String arr[]=line.split("[^a-zA-Z0-9]"); //分割出潛在的單詞
        tolowerCase(arr);
        for(int i=0;i<arr.length;++i) {
            //System.out.println(arr[i]);
            if(arr[i].matches("[a-z]{4}[a-z0-9]*")) {//判斷是不是單詞
                wordCnt++;
                if(!wordCntMap.containsKey(arr[i])) {
                    wordCntMap.put(arr[i], 1);
                }else {
                    wordCntMap.put(arr[i], wordCntMap.get(arr[i])+1);
                }
                
            }
        }
    }

進階需求設計實現過程:

  • 代碼如何組織
    wordCount進階設計了三個類,分別是Main,WordsHandler,Option。Main是程序的入口,實例化一個Option對象,調用該對象的getOption接口獲取操做參數;而後實例化一個WordsHandler對象,調用該對象的readFile讀取文件,並在每讀取一行後調用charCnt,lineCnt,wordCnt進行字符,有效行數,單詞總數統計,若m>1則調用worsCnt進行詞組統計。最後調用writeFile進行輸出。爬蟲採用兩個類,分別是Main,Handler。Main實例化一個Handler,在獲取到目標全部連接後,篩選出具備」content_cvpr_2018/html/「頭部的連接,並使用線程池調用Handle對象的同步方法writeFile進行論文爬取並輸出。正則表達式

  • 單元測試的設計
    算法

  • 代碼組織與內部實現設計(類圖)
    編程

  • 算法的關鍵與關鍵實現部分流程圖
    進階需求較難的地方是單詞詞組的統計,以及爬蟲的效率和同步代碼塊的設計。(因爲Option.isWeight僅僅是權值的不一樣,如下僅解釋當啓用權值的狀況)wordsCnt接收一行字符串,調用getWords分割出全部的可能單詞(存在不符合題目定義"必須4個字母開頭",正則表達式爲"[^a-zA-Z0-9]"),調用getSeperator分割出全部的分隔符(正則表達式爲「[a-zA-Z0-9]+」);而後遍歷單詞數組i=0->letters.length-m查找連續的m個單詞,首先置find爲true,而後從i位置連續遍歷m此單詞數組,若存在一個位置不符合題目單詞的定義則置find=false,二層循環結束後判斷find是否爲true,若爲true則表示存在連續的m個單詞,那麼從位置i開始拼接單詞和其本來的分隔符,並根據該串是在title行仍是在abstract行分別進行map的存取。
//單詞組計數
    public void wordsCnt(String line) {
        if(Option.isWeight) {//啓用權值
            String letters[]=getWords(line); //分割出潛在的單詞
            String separators[]=getSeperator(line);//分割出分隔符
            for(int i=0;i<letters.length-Option.m+1;i++) {
                boolean find=true;
                for(int j=i,cnt=0;cnt<Option.m;++j,cnt++) {
                    if(!letters[j].matches("[a-z]{4}[a-z0-9]*")) {//判斷是不是單詞
                        find=false;
                    }
                }
                if(find) {//找到連續的m個單詞
                    String str=letters[i];
                    for(int j=i+1,cnt=0;cnt<Option.m-1;++j,cnt++) {
                        str+=separators[j-1]+letters[j];
                    }
                    if(line.contains("Title: ")) {
                        if(!wordCntMap.containsKey(str)) {
                            wordCntMap.put(str, 10);
                        }else {
                            wordCntMap.put(str, wordCntMap.get(str)+10);
                        }
                    }
                    if(line.contains("Abstract: ")) {
                        if(!wordCntMap.containsKey(str)) {
                            wordCntMap.put(str, 1);
                        }else {
                            wordCntMap.put(str, wordCntMap.get(str)+1);
                        }
                    }   
                    
                }
            }   
        }
        else {//不啓用權值
            String letters[]=getWords(line); //分割出潛在的單詞
            String separators[]=getSeperator(line);//分割出分隔符
            for(int i=0;i<letters.length-Option.m+1;i++) {
                boolean find=true;
                for(int j=i,cnt=0;cnt<Option.m;++j,cnt++) {
                    if(!letters[j].matches("[a-z]{4}[a-z0-9]*")) {//判斷是不是單詞
                        find=false;
                    }
                }
                if(find) {//找到連續的m個單詞
                    String str=letters[i];
                    for(int j=i+1,cnt=0;cnt<Option.m-1;++j,cnt++)
                        str+=separators[j-1]+letters[j];
                    if(!wordCntMap.containsKey(str)) {
                        wordCntMap.put(str, 1);
                    }else {
                        wordCntMap.put(str, wordCntMap.get(str)+1);
                    }
                }
            }
        }   
    }

爬蟲部分的實現採用jsoup,首先爬取http://openaccess.thecvf.com/CVPR2018.py官網的全部a標籤,而後遍歷全部a標籤數組,若該a標籤的href包含「content_cvpr_2018/html/」則它就是具備論文html的頁面,將其徹底限定路徑取出並使用線程池開啓一個線程去爬取論文。線程會調用Handler的同步方法writeFile(該方法共享cnt變量(論文數量),以及文件指針)對目標論文進行按要求爬取。數組

//Main函數部分代碼
try {
            System.out.println("開始連接");
            Document document=Jsoup.connect("http://openaccess.thecvf.com/CVPR2018.py").maxBodySize(0).timeout(1000*60).get();
            System.out.println("開始爬取");
            handler.writer=new BufferedWriter(new FileWriter("result.txt"));
            //System.out.println(document.toString());
            Elements links=document.getElementsByTag("a");
            int cnt=0;
            for(Element link:links) {
                String href=link.attr("href");
                //System.out.println(href);
                if(href.contains("content_cvpr_2018/html/")) {//獲取論文
                    cnt++;
                    cachedPool.execute(new Runnable() {
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            try {
                                //Thread.sleep(100);
                                Document document=Jsoup.connect("http://openaccess.thecvf.com/"+href).maxBodySize(0).timeout(1000*60*5).get();
                                handler.writeFile(document);
                            } catch (Exception e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }   
                    });
                    
                }
            }
            System.out.println(cnt);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
class Handler{
    BufferedWriter writer;
    int cnt=0;
    synchronized void writeFile(Document document) {//目標內容爬取
        try {
            System.out.println(cnt+" "+document.getElementById("abstract").text());
            String string=cnt+"\r\n";
            string+="Title: "+document.getElementById("papertitle").text()+"\r\n";
            string+="Abstract: "+document.getElementById("abstract").text()+"\r\n\r\n\r\n";
            writer.write(string);
            writer.flush();
            cnt++;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println("close");
        writer.close();
    }
}

改進的思路:

  • 基礎需求的改進:本來採用三模塊子功能劃分致使過分的IO,會使性能降低;改進措施是先對文件掃描一遍導入內存,三個子功能分別對內存中的文件鏡像進行操做,這樣減小了IO,在文件過大的狀況下,能夠有效提升性能。
  • 進階需求的改進:因爲進階是在基礎之上,基礎的改進一樣適用,主要不一樣在於進階須要進行詞組統計,這裏咱們採用了2重循環,最壞狀況下時間複雜度是O(mn),因爲m<=100,因此能夠認爲最壞的時間複雜度是O(n),該出還沒有想到更加的優化策略。爬蟲方面的優化在於本來採用單線程去爬取(有978條數據)使用的時間超過5分鐘,因此咱們就採用了多線程技術,多線程碰到該怎麼設計同步方法,該方法必須儘量的短纔能有效的優化效率,最後抽取出共享cnt變量(論文計數)和文件指針,方法體僅進行目標爬取。

項目測試:

  • 基礎需求的字符,單詞,行數少許計數(有10個測試樣例,這裏只貼出2個)


    微信

  • 基礎需求的壓力測試
    多線程

  • 進階需求的官網論文測試

遇到的困難和解決方法:

  • 1.需求不明確,Edge Case模糊
    解決方法 :在羣裏與同窗,助教交流

  • 2.初次使用java爬蟲
    解決方法 :上網找教程自學爬蟲

  • 3.結對成員上課時間衝突,未能深刻討論代碼實現
    解決方法 :在雙方都沒課時,約個時間討論實現方案;用qq保持交流,實時分享編程進度

  • 4.對於「統計文件的字符數」的功能中一些字符該如何統計未能很好的理解
    解決方法:在微信羣和博客中向助教和老師提問直到完全理解需求,對程序根據助教提供的樣例進行測試

評價隊友

  221600426
隊友積極配合,細心,耐心,具備上進心,兩次做業合做下來很是愉快。
  221600401
個人隊友代碼能力超強!以前只是據說個人隊友隊友是個大佬,結對後才深入體會到他編程的能力,記得今天才剛分好工,次日就完成了基礎的編程,做爲一隊有一種強烈的不能偷懶的想法。個人隊友學習能力也超強,上次做業的墨刀和此次做業的爬蟲,感受很快就能學會而且本身開始作了,對我提出的問題也會認真講解。我但願能學到隊友那超強的編程能力和學習能力,在做業過程當中能不拖累隊友!
相關文章
相關標籤/搜索