1. 程式人生 > >2018軟工實踐第五次作業-結對作業(2)

2018軟工實踐第五次作業-結對作業(2)

txt gettext 需求分析 聲明 bs4 找不到 lock 產生 分類

軟工實踐第五次作業-結對作業(2)

一、結對信息

  • 結對成員
    031602209 陳誌煒
    031602206 陳文垚
  • 本次作業博客鏈接
  • Github項目地址
  • 作業代碼的GitHub地址
  • 具體分工
    陳誌煒 爬取論文信息、完成編碼要求2、3、4、6的代碼編寫、完成附加題
    陳文垚 完成編碼要求5的代碼編寫、單元測試、性能分析

二、PSP表格

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

三、解題思路及設計說明

  • 爬蟲(自己完成)

    運行環境:Python 3.6.6
    使用的庫:requests, lxml, bs4
    通過分析頁面源碼可以看出每篇論文的主頁都是由
<dt class="ptitle"><br><a href="content_cvpr_2018/html/Das_Embodied_Question_Answering_CVPR_2018_paper.html">Embodied Question Answering</a></dt>

@href 屬性 和 http://openaccess.thecvf.com/ 拼接而成的。
所以就先獲取所有論文的主頁,然後再依次爬取所有的論文的信息,利用 lxml, bs4, 正則 提取出
相應位置的信息。 一開始是做單進程的,完整爬一遍要9分鐘多,就改了一個多進程的版本。
全部爬完之後再輸出到文件。

def scrape(lock, data, url):
    try:
        text = get_page(url)

        pdf_links, abstracts, authors, titles, booktitles, months, years = data
        # print(text)
        html = etree.HTML(text)
        pdf_link = html.xpath(‘//a[contains(text(), "pdf")]/@href‘)[0]
        pdf_link = ‘http://openaccess.thecvf.com/‘ + pdf_link[6:]

        abstract = html.xpath(‘//div[@id="abstract"]/text()‘)[0]
        abstract = abstract[1:]

        soup = BeautifulSoup(text, ‘lxml‘)
        detail = soup.find(class_="bibref")

        regex = ‘(\w+) = {([\S\xa0 ]+)}‘
        results = re.findall(regex, detail.getText())
        author = results[0][1]
        title = results[1][1]
        booktitle = results[2][1]
        month = results[3][1]
        year = results[4][1]
        lock.acquire()

        pdf_links.append(pdf_link)
        abstracts.append(abstract)
        authors.append(author)
        titles.append(title)
        booktitles.append(booktitle)
        months.append(month)
        years.append(year)

        lock.release()
    except ConnectionError:
        print(‘Error Occured ‘, url)
    finally:
        print(‘URL ‘, url, ‘ Scraped‘)
  • 代碼組織與內部實現設計(類圖)

    根據需求實現了單詞/詞組的詞頻統計、加入權重的詞頻統計、行數統計、單詞/詞組數統計、字符數
    統計、自定義輸入輸出文件等功能

技術分享圖片

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

    主要就是提取詞組這部分, 要求有不能跨title和abstract,
    兩個部分的統計方式是相同的,所以就先將title和abstract分別存進List,然後再相同方式處理。
    詞組處理,就使用分隔符將title或者abstract進行分割,因為詞組統計的時候要保留分割符,所以
    將所有的分隔符匹配出來備用,詞組統計的時候需要分隔符的時候再連接上去。當詞組數為m時,需要
    連續m個為單詞才滿足條件。
    技術分享圖片

四、附加題設計與展示

五、關鍵代碼解釋

主要部分就是實現詞組統計這一塊

private HashMap<String, Integer> countContent(List<String> contents, int m) {
    HashMap<String, Integer> map = new HashMap<>();
    String splitRegex = "[\\s+\\p{Punct}]+";
    String splitStartRegex = "^[\\s+\\p{Punct}]+";
    String wordRegex = "^[a-zA-Z]{4,}.*";
    Pattern pattern = Pattern.compile(splitRegex);

    for (String content : contents) {
        String[] temp = content.split(splitRegex);

        List<String> splits = new ArrayList<>();
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            splits.add(matcher.group());
        }

        boolean isSplitStart = content.matches(splitStartRegex);

        for (int i = 0; i < temp.length - m + 1; i++) {
            StringBuilder stringBuilder = new StringBuilder();

            if (temp[i].matches(wordRegex)) {
                stringBuilder.append(temp[i]);
            } else {
                continue;
            }
            boolean isContinue = true;
            for (int j = 1; j < m; j++) {
                if (!temp[i + j].matches(wordRegex)) {
                    isContinue = false;
                    break;
                } else {
                    if (isSplitStart) {
                        stringBuilder.append(splits.get(i + j));
                    } else {
                        stringBuilder.append(splits.get(i + j - 1));
                    }
                    stringBuilder.append(temp[i + j]);
                }
            }

            if (isContinue) {
                String words = stringBuilder.toString().toLowerCase();
                if (!map.containsKey(words)) {
                    map.put(words, 1);
                } else {
                    int num = map.get(words);
                    map.put(words, num + 1);
                }
            }

        }

    }
    return map;
}

一開始有想過通過寫正則,直接匹配出符合條件的,emmmm....還是有點難處理。
所以先將句子通過分割符分割,分割出每個詞。匹配出所有的分割符(詞組統計時候,要求帶分隔符連接起來)。
然後直接暴力求解...,判斷出符合條件的詞組。將結果存入map中,並記錄出現的次數。

六、性能分析與改進

測試時使用爬取CVPR的979篇論文作為輸入,命令行參數為-i test.txt -n 15 -m 3 -o output.txt,
使用權重統計詞組詞頻,每三個單詞為一個詞組,輸出詞頻前15的詞組存放到output.txt
循環運行100次,性能分析結果如下

技術分享圖片

技術分享圖片

代碼覆蓋率如下
技術分享圖片

使用VisualVM進行性能分析發現,Main中統計輸入文件詞組詞頻的WordsCount.countContent和統
計輸入文件單詞總數的WordsCount.getWordsSum占用時間最多,占用了90%左右的時間

七、單元測試

在這列出三個單元測試並給出中文註釋,所有的單元測試代碼可以在github中的test項目中看到。

  • 1.對CalMost的單元測試
    測試統計前十個單詞及頻數的函數和統計前n個單詞及頻數的函數
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;

public class CalMostTest {
    String path = "input.txt";
    HandleContent handleContent = new HandleContent(path);
    // -m 詞組單詞數設為1
    // -w 權重設為0
    WordsCount wordsCount = new WordsCount(handleContent, 1, 0);
    HashMap<String, Integer> map = wordsCount.getMap();

    CalMost calMost = new CalMost();

    @Test
    //無 -n 參數輸入時
    //單詞數超過十個則輸出前十個單詞及詞頻
    //不足則輸出所有單詞及詞頻
    public void mostWords() {

        List<Map.Entry<String, Integer>> list = calMost.mostWords(map);
        list.forEach(System.out::println);
    }

    @Test
    //有 -n 參數輸入時
    //單詞數超過n個則輸出前n個單詞及詞頻
    //不足則輸出所有單詞及詞頻
    public void mostWords1() {

        List<Map.Entry<String, Integer>> list = calMost.mostWords(map,9);
        list.forEach(System.out::println);
    }
}
  • 2.對WordsCount的單元測試
    測試獲取單詞數和單詞、詞頻的函數
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class WordsCountTest {

    String path = "input.txt";
    HandleContent handleContent = new HandleContent(path);
    // -m 詞組單詞數設為1
    // -w 權重設為0
    WordsCount wordsCount = new WordsCount(handleContent, 1, 0);

    @Test
    //獲取單詞總數
    public void getSum() {
        System.out.println(wordsCount.getSum());
    }

    @Test
    //獲取map內容,單詞及其詞頻數
    public void getMap() {
        System.out.println(wordsCount.getMap().toString());
    }
}
  • 3.對HandleContent的單元測試
    測試獲取論文title、論文abstract和所有論文內容的函數
import org.junit.Test;

public class HandleContentTest {
    String path = "test.txt";
    //聲明一個handleContent對輸入內容進行分類
    //將所有的title和所有的abstract各自分到一起
    HandleContent handleContent = new HandleContent(path);

    @Test
    //輸出應為所有的title內容
    public void getTitles() {

        System.out.println(handleContent.getTitles().toString());
    }

    @Test
    //輸出應為所有的abstract內容
    public void getAbstracts() {

        System.out.println(handleContent.getAbstracts().toString());
    }

    @Test
    //輸出應為title + abstract 內容
    public void getHandledContent() {

        System.out.println(handleContent.getHandledContent());
    }
}

八、GitHub代碼簽入記錄

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

九、遇到的困難及解決方法

  1. 對於題意的理解存在問題,題目要求標點符號也要算成分隔符,例如question(“orange就可以
    分割成question和orange兩個單詞。但是我們對連字符"-"產生了誤解,認為像Super-Resolution
    這樣的從語義上來看,只能算一個單詞,所以產生了錯誤。經過和助教還有其他同學的溝通後,才明
    白了自己的錯誤。

  2. 粗心導致的問題,在測試時發現統計單詞數時經常會漏掉許多單詞,我們一開始認為是正則出現了
    問題,但是實際上導致這個bug的是循環語句的錯誤,在循環體中本應該使用continue結束本次循環
    但是錯誤地使用了break結束了整個循環,所以導致結果的錯誤。
for (int j = 1; j < m; j++) {
    if (!temp[i + j].matches(wordRegex)) {
    isContinue = false;
        break;
    } else {
        if (isSplitStart) {
            stringBuilder.append(splits.get(i + j));
        } else {
            stringBuilder.append(splits.get(i + j - 1));
        }
        stringBuilder.append(temp[i + j]);
    }
}
  1. 玄學bug, 出現了一次玄學bug,在測試時,讀取文件中的序號後,正則匹配不出來,按行讀取查
    看輸出時沒問題,單獨測試正則也是沒問題的,???,就完全找不到問題,刪除了這個txt文件,重
    新建了一個txt文件,內容完全一樣,再次測試就沒問題了,???。

十、評價隊友

誌煒大佬精通Java、Python,項目經驗豐富,號稱數計最強的男人,本次的結對作業讓我實實在在地體會到了這位巨佬的驚人實力。這次的結對作業對於他來說並沒有什麽難度,但是像我這種剛學Java的人就不一樣了。但是每次在我遇到問題時,他都能夠一眼看出我的問題出在哪裏,告訴我遇到這種問題該如何解決並提出一些新的思路。而且誌煒大佬總是會非常嚴格認真地糾正我的代碼習慣(我之前對這個並不在意)。在他的幫助下,我對Java的學習和理解又得到了更進一步的加深。和誌煒大佬一起合作完成本次作業,讓我的Java學習之旅輕松又愉快。為了感謝誌煒大佬一段時間的幫助,我決定送一套自己的親筆簽名照給他,希望他能夠喜歡(必須喜歡)~~

十一、學習進度條

第N周 新增代碼行 累計代碼行 本周學習耗時(小時) 累計學習耗時(小時) 重要成長
1 200 200 15 15 學習Java以及IDEA的使用
2 10 25 閱讀構建之法,了解了NABCD模型,學會了原型工具的使用
3 600 800 20 45 閱讀《第一行代碼》學習Android開發,Java進一步學習

2018軟工實踐第五次作業-結對作業(2)