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

做業格式


課程名稱:軟件工程1916|W (福州大學)java

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

結對學號:221600415-傅德泉 & 221600416-黃海山github

Github項目地址:基礎篇
Github項目地址:進階篇正則表達式

代碼簽入記錄
算法

做業正文


1、分工狀況

  • 221600415-傅德泉
    • 一、前期需求分析設計以及規劃實現
    • 二、PSP表開發規劃
    • 三、類圖、流程圖討論設計
    • 四、博客文檔主要撰寫
    • 五、目標項目的用例測試
  • 221600416-黃海山
    • 一、前期需求分析設計以及規劃實現
    • 二、類圖、流程圖討論設計
    • 三、代碼撰寫,算法設計與程序功能的實現
    • 四、項目性能分析
    • 五、項目性能優化改進

2、PSP表

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

3、解題思路描述

  • 需求分析:剛拿到這次做業題目的時候,第一感受是有點壓力的,不過在和隊友認真閱讀,按點分析後,咱們從宏觀角度獲得大概的需求框架:基礎需求大概是實現一個命令行程序,將文本輸入進行解析,獲得文本里的字符數單詞數量,有效行數以及top10高頻單詞等。將基本功能封裝後用於進階需求,對論文列表和指定文件進行統計操做。經過編程實現方法,將原來的大問題拆解成各個功能點,分塊解決逐個擊破。
  • 實現思路:主要是利用BufferedReader讀出文件的內容,而後用readLine按行讀出文件,按照特定的分隔字符利用split函數判斷合法性,統計字符數。接着用string類型的List存儲單詞,最後用map按字典序存儲。而在統計字符數功能中,直接調用BufferedReader的read函數,對讀取的字符直接操做。
  • 存在的問題:在實現進階需求時,爬蟲功能以前一直沒有接觸過,可是經過上網學習,結合過去文檔樹相關知識點的認知,很快獲得使用。

4、設計實現過程

一、代碼組織

  • 基本需求
    • 類的組織

      |- src

        |- Main.java(主程序,能夠從命令行接收參數)

        |- lib.java(包含多個其它自定義函數)
    • 函數組織
      • countLetters 統計字符數
      • splitStringIntoList 統計單詞,調用轉小寫函數toLower
      • getContentAndLinesFromFilePath 統計行數,獲取文件內容成字符串,全轉爲小寫
      • getTopTenWords 統計前十字符,其中調用函數getOrderTopTenMap按頻率和字典序排序,而在該函數中再次調用函數sortMapByKey和sortMapByValue,分別實現對Map按key和value進行排序
  • 進階需求
    • 類的組織

      |- cvpr(爬取論文列表,輸出到result.txt)

      |- src

        |- Main.java(主程序,能夠從命令行接收參數)

        |- lib.java(包含多個其它自定義函數)
    • 函數組織
      • countLettersInPapers 封裝基礎功能,在論文裏統計字符數,行數,單詞數。其中調用函數toLower 實現轉小寫包含數字的字符串
      • getTopTenWeightWords 統計前十權重的單詞,調用函數getOrderTopTenMap 使map按頻率和字典序排序。該函數內部調用函數sortMapByKey和sortMapByValue,分別實現對Map按key和value進行排序
      • getTopTenWordarray 統計前十的詞組。函數調用同getTopTenWeightWords

二、單元測試

  • 測試思路:設定十種不一樣的臨界輸入,提早判斷得出測試結果,經過對不一樣input文件進行算法運算得到輸出,判斷與預想結果是否一致
    • 測試getTopTenWeightWords函數
    • 測試countLettersInPapers函數
    • 測試空白文件
    • 測試文件不存在
    • 測試單詞數字開頭的狀況
    • 測試大小寫單詞是否能識別
    • 測試空白行是否識別
    • 測試對\r\n的特殊輸入進行
    • 測試開頭帶有空格、tab的情形
    • 測試帶有空格,非字幕數字符號的分隔符
  • 單元測試截圖

三、代碼組織與類圖

  • 基本需求

  • 進階需求

四、算法關鍵與關鍵實現部分流程圖

(算法的關鍵與關鍵實現部分流程圖--map函數的函數設計)編程

  • 關鍵函數:getOrderTopTenMap--按頻率和字典序排序
  • 實現原理:傳入文本的單詞數組,用map裏的getOrDefaul函數統計全部單詞的頻率,而後將map數組按值排序,再把頻率相同的map分別存到不一樣的子數組中,在對這些子數組裏的map按key排序,在把每一個子數組內的map合併到總map數組中。
  • 算法思惟圖

5、性能分析與改進

一、改進花費時間:95min

二、改進的思路:

  • 因爲一開始是在執行splitStringIntoList(用正則表達式匹配空白符和非字母數字,將整個文本內容字符串切割爲一個個單詞)過程當中,順便用map存儲合法單詞,因爲題目要求是要三個獨立的接口,所以在調用getTopTenWords函數時是先執行了一次splitStringIntoList函數。改進後splitStringIntoList函數只用於統計單詞個數,getTopTenWords則是先自行遍歷一次文件,查找合法單詞後,進行後續操做。
  • 此外,剛開始對文件內容的讀取也存在必定功能冗餘:統計字符函數、統計單詞總數函數和統計有效行三個功能彼此獨立,致使在計算過程當中須要對輸入讀取三次,導致時間資源的浪費。後來經過將文件讀取到內存中,只進行一次輸入操做,從而節省代碼開銷。

三、性能分析圖

四、消耗最大的函數

  • map按頻率和字典序排序函數-- getOrderTopTenMap

6、關鍵代碼

一、基礎需求

  • map按頻率和字典序排序。傳入文本的單詞數組,用map裏的getOrDefaul函數統計全部單詞的頻率,而後將map數組按值排序,再把頻率相同的map分別存到不一樣的子數組中,在對這些子數組裏的map按key排序,在把每一個子數組內的map合併到總map數組中,從而實現題目需求。
public Map<String,Integer>getOrderTopTenMap(Map<String, Integer>m,Integer arrayNum){
        Map<String, Integer> map=sortMapByValue(m);//經過Value排序;
        int order=0;
        int numInten=0;
        for(Integer item:map.values()){
            ++order;
            if(order==arrayNum){
                numInten=item;
            }
        }

        Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry<String, Integer> entry = it.next();
            if(entry.getValue()<numInten)
                it.remove();//使用迭代器的remove()方法刪除元素
        }

        Set<Integer> valueSet=new TreeSet<>();//有序
        for(String key:map.keySet()){//看看有幾種值結果
            valueSet.add(map.get(key));
        }

        Map<Integer,Integer> sameMap=new HashMap<>();//每種結果有多少個
        for(Integer value:valueSet){
            for(String key:map.keySet()){
                if(map.get(key)==value){
                    sameMap.put(value,sameMap.getOrDefault(value,0)+1);
                }
            }
        }
        //相同頻率拆段
        List<Map<String,Integer> > mapList=new ArrayList<>();
        for(Integer item:valueSet){
            Map<String,Integer> mapItem=new HashMap<>();
            for(String s:map.keySet()){
                if(map.get(s)==item){
                    mapItem.put(s,map.get(s));
                }
            }
            mapList.add(mapItem);
        }

        List<Map<String,Integer> > mapList2=new ArrayList<>();
        //頻率相同排序
        for(int i=mapList.size()-1;i>=0;--i){//set升序,頻率最高的段在後面
            mapList2.add(sortMapByKey(mapList.get(i)));
        }
        Map<String,Integer>result=new LinkedHashMap<>();
        int num=0;
        for(int i=0;i<mapList2.size();++i){
            for(String key: mapList2.get(i).keySet()){
                ++num;
                if(num<=arrayNum)
                    result.put(key,mapList2.get(i).get(key));
                else
                    break;
            }
        }
        return result;
    }

二、進階需求

  • 爬取論文信息主函數,經過對網頁DOM樹的操做,獲取頁面信息節點
public static void main(String args[]) throws IOException {
        Document doc=Jsoup.connect(URL).maxBodySize(0).get();
        Elements elements=doc.getElementsByClass("ptitle").select("a");
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(STORE_NAME))));
        int index=0;
        System.out.println("start");
        for(Element e : elements){
            String href=e.attr("href");
            System.out.println(index+":"+href);
            String title=e.text();
            String text=Jsoup.connect(URL_PRE+href).get().getElementById("abstract").text();
            bw.write(String.valueOf(index));
            bw.write("\r\n");
            bw.write("Title: "+title+"\r\n");
            bw.write("Abstract: "+text+"\r\n\r\n");
            ++index;
        }
        bw.flush();
        bw.close();
        System.out.println("end");
    }
  • 統計前十權重單詞,傳入boolean類型的isWeight參數判斷是否須要對單詞進行權重統計,以後按照基礎篇對單詞進行排序的思想處理輸入。
public Map<String,Integer> getTopTenWeightWords(String inputPath,int arrayNum,boolean isWeight)throws Exception{
        Map<String,Integer> map=new HashMap<>();
        FileInputStream fileInputStream=new FileInputStream(new File(inputPath));
        InputStreamReader inputStreamReader=new InputStreamReader(fileInputStream);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String itemStr;
        while((itemStr=br.readLine())!=null){
            if(itemStr.startsWith("Title:")||itemStr.startsWith("Abstract:")){
                String[] strList=itemStr.split(regex);
                int weight=1;
                if(strList[0].equals("Title")){
                    if(isWeight){
                        weight=10;
                    }
                }
                else if(strList[0].equals("Abstract"))
                    weight=1;
                for(int i=1;i<strList.length;++i){
                    String key=strList[i];
                    if(key.matches(wordRegex)){//是一個單詞
                        key=toLower(key);
                        map.put(key,map.getOrDefault(key,0)+weight);
                    }
                }
            }
        }
        br.close();
        return getOrderTopTenMap(map,arrayNum);
    }
  • 統計前十的數組。將數組裏的元素拼接爲一個字符串,以後按照基礎篇中對單詞進行排序的思路排序數組。
public Map<String,Integer> getTopTenWordarray(String inputPath,int num,int lines,boolean isWeight)throws Exception{
        Map<String,Integer> map=new HashMap<>();
        FileInputStream fileInputStream=new FileInputStream(new File(inputPath));
        InputStreamReader inputStreamReader=new InputStreamReader(fileInputStream);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String itemStr;
        while((itemStr=br.readLine())!=null){
            if(itemStr.startsWith("Title:")||itemStr.startsWith("Abstract:")){
                String[] strList=itemStr.split(regex);
                int weight=1;
                if(strList[0].equals("Title")){
                    if(isWeight){
                        weight=10;
                    }
                    for(int i=1;i<strList.length;++i){
                        boolean isArray=true;
                        StringBuilder sb=new StringBuilder();
                        int j;
                        for(j=i;j<i+num&&j<strList.length;++j){
                            String key=strList[j];
                            key=toLower(key);
                            if(!key.matches(wordRegex)){//遇到無效單詞
                                i=j;
                                isArray=false;
                                break;
                            }
                            else{
                                if(sb.length()>0){
                                    sb.append(" "+key);
                                }
                                else{
                                    sb.append(key);
                                }
                            }
                        }
                        if(isArray&&j==num+i) {
                            map.put(sb.toString(), map.getOrDefault(sb.toString(), 0) + weight);
                        }
                    }
                }
                else if(strList[0].equals("Abstract")){
                    weight=1;
                    for(int i=1;i<strList.length;++i){
                        boolean isArray=true;
                        StringBuilder sb=new StringBuilder();
                        int j;
                        for(j=i;j<num+i&&j<strList.length;++j){
                            String key=strList[j];
                            key=toLower(key);
                            if(!key.matches(wordRegex)){//遇到無效單詞
                                i=j;
                                isArray=false;
                                break;
                            }
                            else{
                                if(sb.length()>0){
                                    sb.append(" "+key);
                                }
                                else{
                                    sb.append(key);
                                }
                            }
                        }
                        if(isArray&&j==num+i){
                            map.put(sb.toString(), map.getOrDefault(sb.toString(), 0) + weight);
                        }
                    }
                }
            }
        }
        br.close();
        return getOrderTopTenMap(map,lines);
    }

7、單元測試

  • 測試程序實例
@Test
    public void test1(){
        String input_path="src\\input1.txt";
        try{
            //基本需求
            Lib lib=new Lib();
            String contentStr=lib.getContentAndLinesFromFilePath(input_path);
            List<String> wordList=lib.splitStringIntoList(contentStr);
            Integer lines=lib.getLineCountFromFilePath(input_path); //有效行數
            Integer characters=lib.countLetters(input_path);    //字符數
            Integer words=wordList.size();  //單詞總數
            Map<String,Integer> map=lib.getTopTenWords(wordList,10);
            String topWords ;//頻率最高單詞
            for(String key:map.keySet()){
                topWords="<"+key+">: "+map.get(key)+"\r\n";
            }
            String testChar="characters: 102";
            String testWords="words: 2";
            String testLines="lines: 2";
            String testTopWords="<abcdefghijklmnopqrstuvwxyz>: 2";
            assertEquals(testChar,characters);
            assertEquals(testWords,words);
            assertEquals(testLines,lines);
            assertEquals(testTopWords,topWords);
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
  • 部分測試數據
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
\t\n


測試結果:
characters: 102
words: 2
lines: 2
<abcdefghijklmnopqrstuvwxyz>: 2
abcdefghijklmnopqrstuvwxyz
1234567890
,./;'[]\<>?:"{}|`-=~!@#$%^&*()_+

測試結果:
characters: 76
words: 1
lines: 3
<abcdefghijklmnopqrstuvwxyz>: 1
  • 測試思路:設定不一樣的臨界輸入,如:空白文件、文件不存在、以單詞數字開頭等狀況,提早判斷得出測試結果,經過對不一樣input文件進行算法運算得到輸出,判斷與預想結果是否一致。具體測試內容回見「四-2」。

8、困難與解決方法

  • 在最初的需求分析上花費了較長的時間,一些需求沒有分析到位致使後來在實現過程當中出現遺漏,再補充起來就須要對已實現的代碼進行修改。下次在動手前仍是須要和隊員更加深刻的進行討論。
  • 一些之前沒接觸過的知識在這次編程中須要及時學習,如爬蟲的使用和算法的性能分析。在及時上網,交流掌握技能的同窗,漸漸學到新的實用技能。

9、項目小結

一、隊友評價

  • 221600416-黃海山數組

    在和德泉隊友合做的過程當中,我以爲個人隊友是一個性格溫和,擅於溝通交流的人,不論是閱讀文檔仍是溝通理解的能力都是極強,最讓我感動的是,個人隊友責任心極強,在本身還有許多學生工做要完成的同時,還可以熬夜寫文檔,及時而且高質量地完成做業任務,這實在是一種難能難得的品質。總的來講,這是一次愉快的合做經歷。性能優化

  • 221600415- 傅德泉app

    個人隊友黃海山,編程能力極強,完成了這次做業主要程序的撰寫。在需求分析階段,能較好的相互交流,尊重彼此的見解,使得後邊的工做進展順利。最後在文檔撰寫環節,也能認真交流算法的實現過程,爲後期測試打好基礎,總而言之合做過程十分順利。框架

二、我的心得

  • 221600416-黃海山

    在此次做業開始以前,我曾被此次做業如此多的需求和所要完成的巨大工做量所困惑,可是困難擺在眼前,我顧不得去抱怨什麼,更加不能由於有困難而輕易放棄,因而我不得不開始認真思考如何着眼去解決這個問題,通過對做業需求認真地閱讀和分析後,我發現此次的做業看似繁多的需求實際上是由一個個小問題組成的,而有一部分問題的解答是有重複的,好比做業中對單詞頻率的統計,單詞權重的統計,數組頻率的統計,數組權重的統計均可以歸類爲一個map排序的問題,而其餘諸如單詞,字符的統計等都是熟悉的文件讀寫問題,通過如此分析後,就大大減小了原來的工做量。通過一天多的認真編程後,我和隊友完成了對做業需求的實現和初步測試,又通過一段時間的修改和測試,認真比對做業需求,終於完成了此次做業。在編寫此次做業的過程當中,我最大的收穫就是增強了閱讀文檔的能力,拆分複雜問題的能力。固然,在和隊友的討論過程當中,也增強了溝通交流的能力。

  • 221600415-傅德泉

    這次做業需求量大,鍛鍊到了自身需求分析的能力。在於隊友的溝通交流中,發現本身對於代碼的理解能力也有所提高,同時還接觸了代碼測試的較專業的方式。此外,回顧了類圖和流程圖的繪製方法,更加深刻的掌握了該項能力。在撰寫文檔的過程當中,體會到了一個程序的全面性,應該從多個地方進行考評。

相關文章
相關標籤/搜索