這個做業屬於哪一個課程 | 軟件工程1916-W(福州大學) |
---|---|
這個做業要求在哪裏 | 結對第二次—文獻摘要熱詞統計及進階需求 |
結對學號 | 221600414、221600417 |
Github項目地址 | PairProject1-Java 、PairProject2-Java |
這個做業的目標 | 根據需求進行模塊化編碼,並進行完善和單元測試,熟悉項目開發流程 |
其餘參考文獻 | [1]鄒欣.構建之法[M] |
主要分爲兩個類,Lib類和Main。其中,Lib類做爲一個程序的功能庫,向上提供基礎的API;而Main類主要爲一個程序的入口,經過調用Lib類的API完成程序功能。函數細分至每一個功能點的定義,例如,判斷字符爲分隔符封裝爲一個函數;並對於幾大獨立功能封裝其相應的函數。對於一些較爲複雜關鍵的函數,畫出了相應的流程圖,以便往後的維護以及糾錯。html
爲了方便咱們的」測試工程師「有一個良好的測試體驗,我在不影響結果的狀況下修改Lib類的相關函數,並打包成一個 Jar包;除此以外,編寫了一個基於JUnit的單元測試模板類。」測試工程師「只需經過CV大法導入 JAR 包 和測試模板類,將Jar包添加至項目依賴中,安裝IDEA的JUnit插件,三步操做便可上手測試。java
固然,簡單的單元測試仍是不夠的。每個小功能的正確並不能反映全局的正確性,興許哪個的邏輯在某種關聯的狀況下引起出不同的效果。這時咱們就要上集成測試了,但因爲時間的關係,沒有采用框架進行集成測試,而是直接人工執行+人工校驗結果。python
命令行參數處理: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.單詞計數。使用一個正則表達式進行全文匹配,搜索效率較高。
對於這個需求,能夠聯想到 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")); }
主要的思路已經在上面的關鍵算法中進行展現,下面給出具體的代碼以及一些輔助函數的思路。
// 提取詞組,獲取單詞數 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(); }
分別針對文件中的字符數、有效單詞書以及字典序的單詞頻數作不一樣的單元測試。每一個測試方面都帶有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,測試失敗。
在通過幾回的調試和修改以後,終於所有經過測試,哈哈。
在初步完成了進階需求以後,咱們根據課程做業的要求,本身手動編寫了十餘個測試文件,從最基礎的字符、單詞到複雜的長篇文章,使用命令行一一去測試,而後將測試的結果保存在result。txt中,最後將測試結果和正確答案做對比,而後再進一步去作優化和調試,保證結果的一致性。
maxBodySize(0)
便可解決這個問題。用爬蟲將CVPR2018的官網(http://openaccess.thecvf.com/CVPR2018.py)的全部的論文題目列表以及相關做者的列表爬取下來,而後將爬取下來的數據結構化處理,而後保存到文本中。以後,將內部數據結合外部爬取的數據,以及利用他們之間的聯繫,充分挖掘其中隱藏的數據,並藉助數據可視化技術將他們表示出來。
"""**************爬取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論文發表數量,能夠看出在計算機視覺和模式識別方面的研究投入呈逐年增加的趨勢。
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 |
在此次的項目中,我得到了性能改進以及單元測試的能力。JProfiler是一個易用的 Java 性能分析工具,經過 CPU 佔用時長以此得出函數執行的時間,找出性能瓶頸地方,且加以改進。而 JUnit 是一個實用的單元測試庫,能夠編寫代碼進行測試,代替以往的人工測試,省時省力。除此以外,項目的難度也提升了我問題分析能力,邏輯推理能力,可以對一個問題加以拆解,最終解決。相比於上次的結對編程,我和隊友的配合能力也逐漸提升,再也不是以往的無頭蒼蠅式地工做,而是對任務的分配有了更好地把握,可以發揮出兩我的所擅長的地方,以此提升整個項目的工做效率。
此次做業相比上次做業,不管是在工做量仍是代碼量都比上次多了很多。雖然花費了一週時間(天天三個小時以上)去完成此次做業,欣慰的是,在這個過程當中我學習到了不少東西,使我受益不淺。首先,在寫做業的初期,因爲需求的不明確,前先後後出現了許多問題,不斷去問助教關於需求的問題,由於我明白,搞懂需求,永遠是軟件開發的第一步,也是最重要的一步,邁出了這一步,其他的工做才能順利的進行。其次,在編碼過程當中,我學會了去主動使用單元測試來進行代碼功能的測試,在之前的編碼過程當中,都是邊寫代碼邊測試,沒有養成作完總體系的單元測試的習慣,這可能會致使後期代碼出現一些預料以外的BUG,所以這個好習慣要保持下去,帶到之後的工做崗位上去。