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

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

做業格式

做業正文

分工

  • 221600306
    • 需求分析
    • 結構設計
    • 編碼開發
  • 221600307
    • 需求分析
    • 代碼分析測試
    • 博客撰寫

PSP

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

解題思路

  • 拿到題目以後咱們先是對基礎功能進行了分析,決定初步實現以後再考慮進階需求。做業的基礎功能:編程

    • 統計文件的字符數:
    • 統計文件的單詞總數
    • 統計文件的有效行數
    • 統計文件中各單詞的出現次數,最終只輸出頻率最高的10個。頻率相同的單詞,優先輸出字典序靠前的單詞。
    • 按照字典序輸出到文件result.txt
    • 輸出的單詞統一爲小寫格式

    咱們打算將統計字符、單詞數、行數以及詞頻做爲四個相關聯的函數。選擇編程語言方面,一是對Java比較熟悉,二是考慮到進階需求須要編寫爬蟲,而個人隊友此前已有過用Java編寫爬蟲的經驗,所以咱們選定Java做爲開發語言。app

  • 需求肯定以後進入開發階段,制定代碼規範以及繪製類圖、程序運行流程圖,分配測試任務。查找資料方面,通常是上網搜索,有時也會翻看相關書籍。編程語言

設計實現過程

代碼組織

  • 基本需求函數

    • 代碼方面因爲將主要功能分解成了幾個函數,所以只有一個main類。性能

    • 此類包含四個主要功能函數,topTenWords()統計出現頻率最高的前十個單詞,countLine()統計非空白有效行數,countWord()統計單詞數,countChar()則統計字符數。因爲單個函數的邏輯並不複雜,所以不對單個函數進行流程圖分析,只給出整個類在讀取文件並輸出指定內容過程的主要流程圖。因爲四個函數間互有聯繫,後續函數需用到前邊函數的結果,所以在運行過程當中需遵循必定的函數順序。單元測試

  • 進階需求學習

    • 爬蟲思路:主要使用jsoup(jsoup 是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套很是省力的API,可經過DOM,CSS以及相似於jQuery的操做方法來取出和操做數據 )。先瀏覽CVPR2018官網,獲取頁面的所有代碼,找到須要的內容(論文題目、摘要),利用獲取class、id、tag的方法獲取所需數據,而後將獲取到的數據轉換爲字符串,設置格式後輸出。測試

    • 功能函數:共分紅如下幾個功能函數:countChar()統計字符、countWord()統計單詞、countLine()統計行數、orderWord()統計單詞權重、WordCount2()構造函數。類圖以下:

    • 因爲進階需求是基於基礎需求的開發,所以主要功能邏輯與基礎需求一致,主要區別在於加入了權重的計算,所以只重點給出計算權重的函數orderWord()的流程圖。

      • orderWord

單元測試

  • 利用JUnit對每一個函數即每一個功能進行測試,確保此功能沒有錯誤發生,避免在開發完成後進行統一測試難以找到錯誤。
  • 部分單元測試過程截圖展現


性能分析優化

利用JProfiler對程序進行性能監測。

  • overView

  • Memory

    可看到內存的分配回收過程。

  • CPUview

代碼中最耗時的函數是countWord函數,在測試時也有所體現。

代碼說明

  • 基本需求

    • countChar函數

      public int countChar() {
          int count=0;
          try {
              FileInputStream fileInputStream = new FileInputStream(file);
              int charInt = fileInputStream.read();
              for (count = 0; charInt != -1; count++) {
                  fileCharIntegers.add(charInt);
                  charInt=fileInputStream.read();
              }
              fileInputStream.close();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              //處理回車換行\r\n 13 10 爲一個字符
              for (int i = 0; i < fileCharIntegers.size(); i++) {
                  if (fileCharIntegers.elementAt(i)==13) {
                      if (i<fileCharIntegers.size() && fileCharIntegers.elementAt(i+1)==10) {
                          count--;
                      }
                  }
              }
          return count;
      }

      代碼說明:讀取文件input.txt的內容,由於\r\n算做一個字符,所以統計完字符數以後還需從新判斷相鄰兩字符是不是\r\n,若是是將字符數-1。

    • countWord函數

      for (int i = 0; i < fileCharIntegers.size(); i++) {
          //四個英文字母開頭
          if (Character.isLetter((char)(int)fileCharIntegers.elementAt(i))){              countFourLetter++;      
              sb.append((char)(int)fileCharIntegers.elementAt(i));                        if (countFourLetter>=4 && i == fileCharIntegers.size()-1) {                     strings.add(sb.toString().toLowerCase());
              }
          }else if (Character.isDigit((char
          (int)fileCharIntegers.elementAt(i))&&countFourLetter>=4) {
              sb.append((char)(int)fileCharIntegers.elementAt(i));
              if (countFourLetter>=4 && i == fileCharIntegers.size()-1) {
                  strings.add(sb.toString().toLowerCase());
               }
              //System.out.println("the digit:"+sb.toString());                       }else {
                  if (countFourLetter>=4) {
                      strings.add(sb.toString().toLowerCase());
                  }
                  countFourLetter=0;
                  sb.delete(0, sb.length());
              }
          }

      代碼說明:判斷字符串是否由四個字母開頭,以後對後續字符進行判斷(是不是字母、數字或空白字符),判斷完畢後轉換爲小寫,存入map中。

    • countLine

      while((string = d.readLine()) != null){
                      //System.out.println(count);
                      char chars[] = new char[string.length()];
                      chars=string.toCharArray();
                      for (int i = 0; i < chars.length; i++) {
                          //System.out.println(chars[i]);
                          if ((int)chars[i] > 32 && (int)chars[i] != 127) {
                              count++;
                              break;
                          }
                      }
                  }

      代碼說明:由readLine函數讀取行數,再進行是否包含空白字符的判斷,若不是有效行則不計數。

    • topTenWord

      for (int i = 0; i < strings.size(); i++) {
                  map.put(strings.elementAt(i), 0);
              }
              //統計詞頻
              for (int i = 0; i < strings.size(); i++) {
                  int temp=map.get(strings.elementAt(i));
                  temp++;
                  map.put(strings.elementAt(i), temp);
              }
              //輸出出現頻率最高的10個單詞, map<key,value>以value排序
              //經過ArrayList構造函數把map.entrySet()轉換成list
              list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
              //經過比較器實現比較排序
              Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
                  public int compare(Map.Entry<String, Integer> mapping1, Map.Entry<String, Integer> mapping2) {
                      return mapping2.getValue().compareTo(mapping1.getValue());
                  }
              });

      代碼說明:統計每一個單詞出現頻率,創建單詞與頻率的映射,並按照頻率高低及字母表(頻率一致時參考字母表)排序,將前十個單詞輸出。

  • 進階需求

    • 爬蟲

      public class Main {
          public static final String BASIC_URL = "http://openaccess.thecvf.com/";
      
          public static final String CVRP_URL = "http://openaccess.thecvf.com/CVPR2018.py";
      
          public static List<String> urList = new ArrayList<String>();
      
          public static void main(String[] args) {
              try {
                  /*重定向標準輸出流*/
                  System.setOut(new PrintStream("result.txt"));
                  getPaperUrl();
                  getPaperDetail();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              }   
          }
      
          public static void getPaperDetail() {
              int size;
              size = urList.size();
      //      size = 10;
              for (int i = 0; i < size; i++) {
                  try {
                      Document doc = Jsoup.connect(urList.get(i)).get();
                      Element content = doc.getElementById("content");
                      Element paperTitle = content.getElementById("papertitle");
                      Element paperAbstract = content.getElementById("abstract");
                      System.out.println(i);
                      System.out.println("Title: "+paperTitle.text());
                      System.out.println("Abstract: "+paperAbstract.text()+"\n\n");
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public static void getPaperUrl() {
              try {
                  Document doc = Jsoup.connect(CVRP_URL).get();
      //          System.out.println(doc);
                  Element content = doc.getElementById("content");
                  Elements ptitles = content.getElementsByClass("ptitle");
                  for (int i = 0; i < ptitles.size(); i++) {
                      String link = ptitles.get(i).getElementsByTag("a").attr("href");
                      urList.add(BASIC_URL+link);
      //              System.out.println(BASIC_URL+link);
                  }       
      
              } catch (IOException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          }
      }

      代碼說明:獲取整個網頁HTML代碼後再根據<content><ptitle><a href>等標籤獲取文章標題、摘要、連接等內容,重定向輸出後設置要求格式輸出。

    • 進階需求主要功能函數

      • countWord

        public int countWord() {
              numOfWord=0;
              for (int i = 0; i < titleList.size(); i++) {
                  int countFourLetter = 0;
                  StringBuffer sb = new StringBuffer();
                  for (int j = 0; j < titleList.get(i).length(); j++) {
                      //四個英文字母開頭
                      if (Character.isLetter(titleList.get(i).charAt(j))) {
                          countFourLetter++;      
                          sb.append(titleList.get(i).charAt(j));
                          if (countFourLetter>=4 && j == titleList.get(i).length()-1) {
                              if (titleWordMap.get(sb.toString())!=null) {
                                  int temp = titleWordMap.get(sb.toString());
                                  temp++;
                                  titleWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  titleWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                      }else if (Character.isDigit(titleList.get(i).charAt(j))&&countFourLetter>=4) {
                          sb.append(titleList.get(i).charAt(j));
                          if (countFourLetter>=4 && j == titleList.get(i).length()-1) {
                              if (titleWordMap.get(sb.toString())!=null) {
                                  int temp = titleWordMap.get(sb.toString());
                                  temp++;
                                  titleWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  titleWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                          //System.out.println("the digit:"+sb.toString());           
                      }else {
                          if (countFourLetter>=4) {
                              if (titleWordMap.get(sb.toString())!=null) {
                                  int temp = titleWordMap.get(sb.toString());
                                  temp++;
                                  titleWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  titleWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                          countFourLetter=0;
                          sb.delete(0, sb.length());
                      }
                  }
              }
              for (int i = 0; i < abstractList.size(); i++) {
                  int countFourLetter = 0;
                  StringBuffer sb = new StringBuffer();
                  for (int j = 0; j < abstractList.get(i).length(); j++) {
                      //四個英文字母開頭
                      if (Character.isLetter(abstractList.get(i).charAt(j))) {
                          countFourLetter++;      
                          sb.append(abstractList.get(i).charAt(j));
                          if (countFourLetter>=4 && j == abstractList.get(i).length()-1) {
                              if (abstractWordMap.get(sb.toString())!=null) {
                                  int temp = abstractWordMap.get(sb.toString());
                                  temp++;
                                  abstractWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  abstractWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                      }else if (Character.isDigit(abstractList.get(i).charAt(j))&&countFourLetter>=4) {
                          sb.append(abstractList.get(i).charAt(j));
                          if (countFourLetter>=4 && j == abstractList.get(i).length()-1) {
                              if (abstractWordMap.get(sb.toString())!=null) {
                                  int temp = abstractWordMap.get(sb.toString());
                                  temp++;
                                  abstractWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  abstractWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                          //System.out.println("the digit:"+sb.toString());           
                      }else {
                          if (countFourLetter>=4) {
                              if (abstractWordMap.get(sb.toString())!=null) {
                                  int temp = abstractWordMap.get(sb.toString());
                                  temp++;
                                  abstractWordMap.put(sb.toString().toLowerCase(), temp);
                              }else {
                                  abstractWordMap.put(sb.toString().toLowerCase(), 1);
                              }
                              numOfWord++;
                          }
                          countFourLetter=0;
                          sb.delete(0, sb.length());
                      }
                  }
              }
        //        System.out.println(titleWordMap);
        //        System.out.println(abstractWordMap);
              return numOfWord;
          }

        代碼說明:讀入字符以後進行單詞判斷,而後根據標題和摘要標籤進行分類存入List。

      • orderWord

        public void orderWord(int weightOfTitle, int weightOfAbstract) {
              //title和abstract中的單詞的並集
              Set<String> titleSet = titleWordMap.keySet();
              Set<String> abstractSet = abstractWordMap.keySet();
              Set<String> totalSet = new HashSet<String>();
              totalSet.addAll(titleSet);
              totalSet.addAll(abstractSet);
        
              //帶上權重從新計算詞頻
              Iterator<String> setIterator = totalSet.iterator();
              while(setIterator.hasNext()) {
                  String key = setIterator.next();
                  int titleValue = titleWordMap.get(key)!=null ? titleWordMap.get(key):0;
                  int abstractValue = abstractWordMap.get(key)!=null ? abstractWordMap.get(key):0;
                  totalWordMap.put(key,titleValue*weightOfTitle+abstractValue*weightOfAbstract);
              }
        
              //按照詞頻排個序
              //經過ArrayList構造函數把map.entrySet()轉換成list
                orderedWordList = new ArrayList<Map.Entry<String, Integer>>(totalWordMap.entrySet());
                //經過比較器實現比較排序
                Collections.sort(orderedWordList, new Comparator<Map.Entry<String, Integer>>() {
                    public int compare(Map.Entry<String, Integer> mapping1, Map.Entry<String, Integer> mapping2) {
                        return mapping2.getValue().compareTo(mapping1.getValue());
                    }
                });
          }
      • WordCount2

        public WordCount2(String filePath) {
              try {
                  DataInputStream dataInputStream = new DataInputStream(new FileInputStream(filePath));
                  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(dataInputStream));
                  String string;
        
                  while((string=bufferedReader.readLine()) != null) {
                      if (string.matches("^\\d+")) {//論文編號
                          char[] cbuf = new char[10];
                          bufferedReader.read(cbuf,0,7);
        
                          //title裏的單詞
                          String titleString = bufferedReader.readLine();
                          titleList.add(titleString);
                          //System.out.println(titleString);
        
                          //abstract裏的單詞
                          bufferedReader.read(cbuf,0,10);
                          String abstractString = bufferedReader.readLine();
                          abstractList.add(abstractString);
                          //System.out.println(abstractString);
        
                          //空格兩行不計
                          bufferedReader.readLine();
                          bufferedReader.readLine();
        
                      }
                  }
                  dataInputStream.close();
                  bufferedReader.close();
        
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }

測試

  • 測試環節咱們使用白盒測試用例設計方法來設計測試用例。白盒測試有六種覆蓋標準:語句覆蓋、斷定覆蓋、條件覆蓋、斷定/條件覆蓋、條件組合覆蓋和路徑覆蓋,發現錯誤的能力呈由弱至強的變化。 經過研究需求,咱們認爲應重點測試的狀況有如下幾種:

    • 空白行是否被算做有效行計入行數
    • 換行符數目的計算
    • 單詞的大小寫不一樣是否被認爲是不一樣的單詞
    • \r\n字符數的認定
    • 空白文檔或只含分隔符、換行符等的文檔的數據統計
  • 部分測試代碼

    public class MainTest {
    
      Main wordcount = new Main("D:\\學習\\軟工實踐\\wordcount\\input.txt");
    
      @Test
      public void testCountChar() {
          int numOfChar=wordcount.countChar();
          int testChar=33;
          assertEquals(testChar,numOfChar);
      }
    
      @Test
      public void testCountWord() {
          int numOfWord=wordcount.countWord();
          int testWords=2;
          assertEquals(testWords,numOfWord);
      }
    
      @Test
      public void testCountLine() {
          int numOfLine=wordcount.countLine();
          int testLine=4;
          assertEquals(testLine,numOfLine);
      }
    
      @Test
      public void testTopTenWord() {
          List<Map.Entry<String, Integer>> list = wordcount.topTenWord();
          String[] testWord= {"snakesre5","name"};
          String[] testNum= {"1","1"};
          for(int i=0;i<10&&i<list.size();i++)
          {
              assertEquals(testWord[i],list.get(i).getKey());
              assertEquals(testNum[i],list.get(i).getValue());
          }
    
      }
    
    }
  • 一些測試樣例

    編號 測試用例 預期結果 運行結果
    1 空白文檔 characters:0
    words:0
    lines:0
    characters:0
    words:0
    lines:0
    2 含三個換行符 characters:3
    words:0
    lines:0
    characters:3
    words:0
    lines:0
    3 三個換行符+ Name characters:7
    words:1
    lines:1
    <name>:1
    characters:7
    words:1
    lines:1
    <name>:1
    4 \r\n hi
    asind asiwdb edfgEfDG AsiWdB
    ASIWDB aSiWdB
    characters: 61
    words: 7
    lines: 3
    <asiwdb>: 4
    <asind>: 1
    <edfg>: 1
    <efdg>: 1
    characters: 61
    words: 7
    lines: 3
    <asiwdb>: 4
    <asind>: 1
    <edfg>: 1
    <efdg>: 1
    5 hn
    ji o.xs
    ijml9648 ijml9748
    78964152sdcf @)754
    ^&*()
    characters: 55
    words: 3
    lines: 5
    <ijml9648>: 1
    <ijml9748>: 1
    <sdcf>: 1
    characters: 55
    words: 3
    lines: 5
    <ijml9648>: 1
    <ijml9748>: 1
    <sdcf>: 1

總結

  • 221600306
    • 在這次結對過程當中,比較實際地應用了版本控制,雖然看了commit message書寫的博客,可是因爲經驗不足,仍是不太可以熟練地表達代碼的更改,詳略或許不太得當。
    • 這次做業我負責功能代碼的編寫,隊友負責測試及博客的撰寫。結對過程當中我與隊友互相充當了對方的領航員,保持了良好的交流合做,一塊兒熬夜寫代碼也不失爲一次難忘的體驗!
  • 221600307
    • 此次做業最大的收穫是學會了如何對代碼進行性能分析和怎樣在開發過程當中、後進行測試,在以後的學習工做中必定可以更好的對代碼進行優化。遇到的主要困難是以前沒有作過測試,如何設置測試數據、怎樣才能全面地對程序進行覆蓋不遺漏錯誤是很是值得思考的。再加上對JProfiler不熟悉,使用的過程當中看懂各類分析也花費了一點時間。
    • 對隊友的評價:個人隊友代碼能力很強,在我作測試無處下手時也給了我很大幫助。咱們不是第一次結對,每一次結對完成任務的過程當中都不多出現分歧,她是一個很是好的合做者,相信在過去及將來的合做學習中咱們還能從對方身上學到更多。
相關文章
相關標籤/搜索