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

做業格式

這個做業屬於哪一個課程 軟件工程1916-W(福州大學)
這個做業要求在哪裏 結對第二次—文獻摘要熱詞統計及進階需求
結對學號 221600414221600417
Github項目地址 PairProject1-JavaPairProject2-Java
這個做業的目標 根據需求進行模塊化編碼,並進行完善和單元測試,熟悉項目開發流程
其餘參考文獻 [1]鄒欣.構建之法[M]

Github代碼簽入記錄

PairProject1-Java:

PairProject2-Java:

具體分工

黃樂興:

  • 基本需求項目和進階項目的編寫;代碼調優;

馮凱:

  • 需求分析;附加題編寫;單元測試;文檔書寫;

解題思路描述

1.WordCount:

  • 初期:當我看到這個題目信息時,就發現此次的需求並不簡單,甚至很難理解。所以,我花了數天的時間不斷和同窗一塊兒探討需求,理解思路,明白每一點需求中的具體含義。由於, 若是需求沒法正確地解讀,寫出的程序代碼也會有N多個坑,而填坑的過程花費的時間將會遠遠超過挖坑的時間。
  • 中期:對於一些功能點的實現,例如:文件的讀寫,因爲使用的頻率較低,API使用早已忘記。採用面向搜索引擎編程,Google + StackOverFlow 提問式搜索,短期最大效率學習相關API。 而對於一些隱約在腦海記得的API,直接鼠標點擊相關類查看其中的源碼,配合源碼的註釋,進而再次掌握這個API。

2.論文信息爬取:

  • 工具:Jsoup
  • 思路:經過 Jsoup 請求指定url(http://openaccess.thecvf.com/CVPR2018.py),獲取返回 Document 對象。接着定位在 dl 標籤(論文信息所在位置),使用一個循環,獲取每個篇論文信息。其中,每一篇的論文信息的標題位於第一個 a 標籤的文本信息中,而摘要信息url位於這個標籤的 href 屬性中。繼續經過訪問這個摘要信息url,並爬取 id 爲 abstract 的標籤的文本信息即摘要信息。

設計實現過程

1.代碼組織:

主要分爲兩個類,Lib類和Main。其中,Lib類做爲一個程序的功能庫,向上提供基礎的API;而Main類主要爲一個程序的入口,經過調用Lib類的API完成程序功能。函數細分至每一個功能點的定義,例如,判斷字符爲分隔符封裝爲一個函數;並對於幾大獨立功能封裝其相應的函數。對於一些較爲複雜關鍵的函數,畫出了相應的流程圖,以便往後的維護以及糾錯。html

2.單元測試:

爲了方便咱們的」測試工程師「有一個良好的測試體驗,我在不影響結果的狀況下修改Lib類的相關函數,並打包成一個 Jar包;除此以外,編寫了一個基於JUnit的單元測試模板類。」測試工程師「只需經過CV大法導入 JAR 包 和測試模板類,將Jar包添加至項目依賴中,安裝IDEA的JUnit插件,三步操做便可上手測試。java

固然,簡單的單元測試仍是不夠的。每個小功能的正確並不能反映全局的正確性,興許哪個的邏輯在某種關聯的狀況下引起出不同的效果。這時咱們就要上集成測試了,但因爲時間的關係,沒有采用框架進行集成測試,而是直接人工執行+人工校驗結果。python

3.關鍵算法及流程圖

命令行參數處理:git

一種方式是遍歷 String[] args,每次獲取兩個字符串,並經過值來進行相應的處理。但這種方式須要寫大量的if else 分支判斷條件,每次增長新的參數,還必須修改原有的代碼,不符合開閉原則。通過分析以後,咱們得出第二個更爲合理的方法,使用一個Map對命令行參數進行封裝。後續對於命令行的查找只需經過Map.get(),且新增參數後也沒必要修改以前的代碼。github

單詞計數:正則表達式

定義兩個輔助變量,letterCheck 默認爲-4,letterCheckAble 默認爲 true。第一個變量用於判斷前綴字母數是否大於等於4,第二個變量用因而否須要進行單詞檢測。逐個字符遍歷字符串,當檢測到非字母時,判斷letterCheck是否爲0,若是爲0則將 letterCheckAble 賦值爲 false,關閉單詞檢測,直到遇到一個分隔符則再次打開檢測開關;當檢測到字母時,將 letterCheck 自增直至爲0;當檢測到分隔符時,判斷單詞檢測是否爲打開狀態且letterCheck 爲0,若是是的話則把單詞數自增。算法

長度爲N詞組的提取處理:編程

第一步:對字符串進行切割,分爲兩類,一類爲分隔符字符串,另外一個爲非分隔符字符串。具體操做爲,使用Matcher 正則表達式,不斷匹配相應的字符串,並放在一個字符串鏈表中。切割完以後,能夠獲得一個分隔符字符串鏈表和非分隔符字符串鏈表。微信

第二步:使用雙指針L和R,R從0開始到非分隔符字符串鏈表 list 的尾部,不斷遍歷。在每次的遍歷中,判斷 list.get(R) 是否爲單詞。若是爲單詞而且 R-L+1 == N,則已經找到一個合法詞組的座標範圍(L-R),進而合併這些單詞做爲詞組,放置在Map中;若是不爲單詞,則將L賦值爲R+1,使得下一次R遍歷的時候指針L和R再次重疊在一個地方。數據結構

性能分析與改進

性能分析圖以及消耗最大的函數:

此圖爲經過 JProfiler 調優工具獲取。佔用時間較長的大部分爲系統庫函數,前幾個函數中只有三個出如今代碼中。消耗最大的函數應該爲 FileOutputStream.close() ,目前尚不清楚爲啥佔用時間較長。而執行次數最多的爲 String.charAt()

改進的思路:

代碼優化:

初始化 BufferedReader 的默認大小爲文件長度,這樣只需一次IO便可將整個文件讀取進內存,而以前的默認大小是固定的。但在幾回嘗試以後,並無時間上的增進,多是文件不夠大的緣由。

算法優化:

1.多個字符串尋找連續的長度N的合法詞組。使用雙指針進行搜索,能夠減小判斷的次數。

2.單詞計數。使用一個正則表達式進行全文匹配,搜索效率較高。

關鍵代碼展現與說明

1.MAP 自定義排序 + 分割 + 輸出

對於這個需求,能夠聯想到 JAVA8 的一個新特性,流處理。將集合看作爲一個流,流在管道運輸中加入各類處理,例如排序,限制,循環等,便可在較少的代碼量中完成一個複雜的功能。

// 排序Map並輸出
static void sortMapAndOut(Map<String, Integer> map, StringBuilder builder) {
    map.entrySet()
        .stream()
        .sorted((e1, e2) -> {
            int cmp = e2.getValue().compareTo(e1.getValue());
            if (cmp == 0) return e1.getKey().compareTo(e2.getKey());
            else return cmp;
        })
        .limit(10)
        .forEach(o -> builder.append("<").append(o.getKey()).append(">").append(":      ").append(o.getValue()).append("\n"));
}

2.長度爲N詞組的提取處理:

主要的思路已經在上面的關鍵算法中進行展現,下面給出具體的代碼以及一些輔助函數的思路。

// 提取詞組,獲取單詞數
static int countWord(String s, int w, int len, Map<String, Integer> map) {
    int wordNum = 0;
    boolean isDivBegin = isDivision(s.charAt(0));
    List<String> titles = cutStr(s, DIV_RE);
    List<String> titles2 = cutStr(s, NOT_DIV_RE);
    for (int i = 0, j = i; i < titles.size(); i++) {
        if (isWord(titles.get(i))) {
            wordNum++;
            if ((i - j + 1) == len) {
                String word = getWord(isDivBegin, titles, titles2, j, i).toLowerCase();
                map.merge(word, w, (a, b) -> a + b);
                j++;
            }
        } else {
            j = i + 1;
        }
    }
    return wordNum;
}

此輔助函數爲拼接i-j範圍的合法單詞以及分割符字符串。

這裏存在兩個鏈表,其中s爲合法單詞鏈表,而s2爲分割字符串鏈表。經過下標之間的關係咱們能夠得出一個結論,當未切割字符串的第一個字符爲字母時,拼接過程當中切割字符串的下標等於j,而當第一個字符爲非字母時,切割字符串的小標等於j+1。由此,咱們能夠經過這個規律對拼接這兩個鏈表。

// 拼接字符串
private static String getWord(boolean isDivBegin, List<String> s, List<String> s2, int j, int i) {
    StringBuilder builder = new StringBuilder();
    int offset = isDivBegin ? 1 : 0;
    while (j <= i) {
        builder.append(s.get(j));
        if (j != i) builder.append(s2.get(j + offset));
        j++;
    }
    return builder.toString();
}

部分測試代碼展現與說明

1.基礎需求單元測試

分別針對文件中的字符數、有效單詞書以及字典序的單詞頻數作不一樣的單元測試。每一個測試方面都帶有5個以上的測試點,覆蓋大多數可能出現的狀況。

/***********部分單元測試代碼****************/
private void newFile(String s) throws IOException {
    BufferedOutputStream bf = new BufferedOutputStream(new              FileOutputStream(TEST_FILE_NAME));
    bf.write(s.getBytes());
    bf.flush();
}

/*
* **測試文件中字符的個數**
* 主要測試點:轉義字符、字母、數字及其餘字符任意組合的個數
* 例:\\\"123abc!@#
* */
@Test
void testCharNum2() throws IOException {
    newFile("\"\'\\26384 hfJFD *-.@!");
    int charNum = CountUtil.getCharNum(TEST_FILE_NAME);
    Assertions.assertEquals(20, charNum);
}

/*
* **測試文件中單詞的個數**
* 主要測試點:不能以數字開頭,字母(4個開頭)和數字的任意組合,以特殊字符分割,不區分大小寫
* 例:file12desk%losses225
* */
@Test
void testWordNum3() throws IOException {
    newFile("c2ools DisCount23-hayerS SELLER*CANcels#GAY9220^ 89NAVY!!)(SwingS=flying290");
    int letterNum = CountUtil.getLetteryNum(TEST_FILE_NAME);
    Assertions.assertEquals(6, letterNum);
}
/*
* **測試文件中各單詞出現的次數**
* 主要測試點:至少以4個字母開頭,不區分大小寫,後跟字母和數字的任意組合,以特殊字符分割
* */
@Test
void testMaxWord4() throws IOException {
    newFile("sex23 gold89&numbers&&&&90byes (cLicks009(clicks009)gold89 sexx )) shopping-NUMBERS265clicls");
    LinkedHashMap<String, Integer> result = CountUtil.getMaxLetter(TEST_FILE_NAME);
    System.out.println(result);
    Assertions.assertEquals(1, 0);
}

初期測試過程當中出現了一些BUG,測試失敗。

在通過幾回的調試和修改以後,終於所有經過測試,哈哈。

2.進階需求測試

在初步完成了進階需求以後,咱們根據課程做業的要求,本身手動編寫了十餘個測試文件,從最基礎的字符、單詞到複雜的長篇文章,使用命令行一一去測試,而後將測試的結果保存在result。txt中,最後將測試結果和正確答案做對比,而後再進一步去作優化和調試,保證結果的一致性。

遇到的困難及解決方法

HLXING:

  • 需求不明,遲遲沒法理解。經過微信提問的方式+同窗之間交流解決。
  • 爬蟲獲取的數據少了一半。Google 提問,發現是這個爬蟲庫的 API 存在設計上的缺陷,反人類的默認響應數據包大小1MB的設定,只需加個 maxBodySize(0) 便可解決這個問題。
  • 測試結果與其它同窗不符合。經過折半糾錯法,不斷刪減輸入文件的內容,最後肯定問題出如今一個非ASCII字符(中文下的上引號)

KAI:

  • 在搞測試的時候,剛開始設計的測試樣例都比較普通,沒有針對性,所以很難測出程序中存在的問題。在看了一些別的組的測試樣例以後,有了靈感,寫出了好多針對字符、單詞、字典序排序的樣例,這些樣例針對最可能出現問題的地方進行測試,果真,發現了很多的BUG,最終得以解決完善。
  • 爬取數據進行數據挖掘分析的時候,將爬取的數據存放在文件中。面對雜亂無章的數據,不知道如何從它們中抽取有用信息。在通過一番思考以後,將不一樣類型的數據進行結構化存儲,而後按照不一樣的類別,從它們中提取有用信息,去除無用信息,最後加以可視化處理,將它們之間的關係很清晰的展現出來。

附加題設計與展現

1.設計思路:

用爬蟲將CVPR2018的官網(http://openaccess.thecvf.com/CVPR2018.py)的全部的論文題目列表以及相關做者的列表爬取下來,而後將爬取下來的數據結構化處理,而後保存到文本中。以後,將內部數據結合外部爬取的數據,以及利用他們之間的聯繫,充分挖掘其中隱藏的數據,並藉助數據可視化技術將他們表示出來。

2.代碼展現:

"""**************爬取CVPR首頁源碼************"""
import requests

BASE_URL = "http://openaccess.thecvf.com/CVPR2018.py"

try:
    html = requests.get(BASE_URL)
    with open("index.html", "wt", errors="ignore") as f:
        f.write(html.text)
        except Exception as e:
            print(str(e))

將論文標題加以切詞處理,而後根據其出現的頻率,繪製熱點研究方向的詞雲圖

"""***********部分繪圖代碼***********"""
with open("titles.txt", encoding="utf-8")as file:
    text = file.read()
    words = chinese_jieba(text)
    wordcloud = WordCloud(font_path="C:/Windows/Fonts/simhei.ttf",
                          background_color="white", width=800,
                          height=400, max_words=50, min_font_size=8).generate(words)
    image = wordcloud.to_image()
    image.show()

利用數據挖掘技術,將論文發表所在的學術機構(高校、研究院、實驗室)所發表的論文數量加以統計,繪製柱狀圖,從一方面展現展現這些學術機構的研究能力。

"""***********部分繪圖代碼***********"""
def collect_univ():
    univ_count = {}
    with open("university_count.txt", "rt", encoding='utf-8') as f:
        word = f.readline()
        while word:
            count = univ_count.get(word.strip(), None)
            if count:
                univ_count[word.strip()] += 1
            else:
                univ_count.setdefault(word.strip(), 1)
                word = f.readline()
    return sorted(univ_count.items(), key=lambda x: x[1], reverse=True)
def draw_graph():
    univ_list = dict(collect_univ())
    x = list(univ_list.keys())[:8]
    y = list(univ_list.values())[:8]
    plt.bar(x, y, alpha=1.0, width=0.7, color=(0.1, 0.5, 0.8), label=None)
    plt.xlabel("Research Institute", fontsize=15)
    plt.xticks(x, rotation=20)
    plt.ylabel("Number of papers", fontsize=15)
    plt.tick_params(axis='x', labelsize=7)

經過全部論文的做者之間的關係,將各個做者參與發表的論文數量加以統計,展現出科研能力比較強的一些做者。

def load_author():
    soup = BeautifulSoup(open("../CVPR_Spider/index.html"), "html.parser")
    authors = soup.find_all('a', href="#")
    f = open("authors.txt", "wt", encoding='utf-8')
    for author in authors:
        filtered_author = author.text.replace(' ', '').replace('\n', ' ')
        f.write(filtered_author + '\n')
        f.close()
def analyse_authors():
    author_count = {}
    f = open("authors.txt", "rt", encoding='utf-8')
    author = f.readline()
    while author:
        count = author_count.get(author.strip(), None)
        if count:
            author_count[author.strip()] += 1
        else:
            author_count.setdefault(author.strip(), 1)
            author = f.readline()
    return sorted(author_count.items(), key=lambda x: x[1],reverse=True)

經過爬取往年的CVPR論文發表數量,能夠看出在計算機視覺和模式識別方面的研究投入呈逐年增加的趨勢。

3.相關附件:源碼和圖表

PSP表格

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

評價你的隊友

1.值得學習的地方

這個隊友很細心,有較強的源碼閱讀能力,可以在看似密不透風的代碼中,找到一個大坑,並分析出這個大坑出現的緣由。除此以外,熟練掌握 Python 語言,擅長數據挖掘,並搜尋出數據之間的關係。

2.須要改進的地方

不熬夜敲代碼,多鍛鍊身體!

項目總結

HLXING:

在此次的項目中,我得到了性能改進以及單元測試的能力。JProfiler是一個易用的 Java 性能分析工具,經過 CPU 佔用時長以此得出函數執行的時間,找出性能瓶頸地方,且加以改進。而 JUnit 是一個實用的單元測試庫,能夠編寫代碼進行測試,代替以往的人工測試,省時省力。除此以外,項目的難度也提升了我問題分析能力,邏輯推理能力,可以對一個問題加以拆解,最終解決。相比於上次的結對編程,我和隊友的配合能力也逐漸提升,再也不是以往的無頭蒼蠅式地工做,而是對任務的分配有了更好地把握,可以發揮出兩我的所擅長的地方,以此提升整個項目的工做效率。

KAI:

此次做業相比上次做業,不管是在工做量仍是代碼量都比上次多了很多。雖然花費了一週時間(天天三個小時以上)去完成此次做業,欣慰的是,在這個過程當中我學習到了不少東西,使我受益不淺。首先,在寫做業的初期,因爲需求的不明確,前先後後出現了許多問題,不斷去問助教關於需求的問題,由於我明白,搞懂需求,永遠是軟件開發的第一步,也是最重要的一步,邁出了這一步,其他的工做才能順利的進行。其次,在編碼過程當中,我學會了去主動使用單元測試來進行代碼功能的測試,在之前的編碼過程當中,都是邊寫代碼邊測試,沒有養成作完總體系的單元測試的習慣,這可能會致使後期代碼出現一些預料以外的BUG,所以這個好習慣要保持下去,帶到之後的工做崗位上去。

相關文章
相關標籤/搜索