2018軟工實踐第五次作業-結對作業(2)
軟工實踐第五次作業-結對作業(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代碼簽入記錄
九、遇到的困難及解決方法
對於題意的理解存在問題,題目要求標點符號也要算成分隔符,例如question(“orange就可以
分割成question和orange兩個單詞。但是我們對連字符"-"產生了誤解,認為像Super-Resolution
這樣的從語義上來看,只能算一個單詞,所以產生了錯誤。經過和助教還有其他同學的溝通後,才明
白了自己的錯誤。- 粗心導致的問題,在測試時發現統計單詞數時經常會漏掉許多單詞,我們一開始認為是正則出現了
問題,但是實際上導致這個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]);
}
}
- 玄學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)