18軟工實踐-第五次作業-結對作業2
結對同學的博客鏈接
本作業博客的鏈接
Github項目地址
附加功能代碼
具體分工
莊卉:爬蟲、詞頻權重計算+自定義統計輸出、附加功能、博客撰寫
胡緒佩:詞組詞頻統計功能+字符及有效行數目、單元測試、性能分析、博客撰寫
PSP表格
PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘) Planning 計劃 30 30 · Estimate · 估計這個任務需要多少時間 30 30 Development 開發 1770 1740 · Analysis · 需求分析 (包括學習新技術) 120 150 · Design Spec · 生成設計文檔 30 30 · Design Review · 設計復審 30 30 · Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 30 60 · Design · 具體設計 120 90 · Coding · 具體編碼 900 720 · Code Review · 代碼復審 180 180 · Test · 測試(自我測試,修改代碼,提交修改) 360 480 Reporting 報告 120 120 · Test Repor · 測試報告 60 30 · Size Measurement · 計算工作量 30 20 · Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 30 70 合計 1920 1890 解題思路描述與設計實現說明
爬蟲使用
爬蟲語言使用的是python(為什麽沒使用java至今還是個迷,對沒加成分有點心痛
用urllib.request抓取頂會論文列表網址得到源代碼,beautifulsoup配合正則(附加功能爬蟲使用到)即可得到論文列表的基本信息。
f = open("result.txt", ‘a‘, encoding=‘utf-8‘) html1 = urllib.request.urlopen("http://openaccess.thecvf.com/CVPR2018.py").read() bf1 = BeautifulSoup(html1) texts1 = bf1.select(‘.ptitle‘) a_bf = BeautifulSoup(str(texts1)) a = a_bf.find_all(‘a‘) urls = [] for each in a: urls.append("http://openaccess.thecvf.com/" + each.get(‘href‘)) for i in range(len(urls)): f.write(str(i)) f.write("\n") html2 = urllib.request.urlopen(urls[i]).read() bf = BeautifulSoup(html2) texts2 = bf.find_all(‘div‘, id=‘papertitle‘) f.write("Title: " + texts2[0].text.lstrip(‘\n‘) + "\n") texts3 = bf.find_all(‘div‘, id=‘abstract‘) f.write("Abstract: " + texts3[0].text.lstrip(‘\n‘) + "\n\n\n")
代碼組織與內部實現設計
代碼文件組織
031602114&031602444
|- src
|- WordCount.sln
|- WordCount
|- CharCount.cpp
|- CharCount.h
|- LineCount.cpp
|- LineCount.h
|- WeightTypeNE.cpp
|- WeightTypeNE.h
|- SortTopN.cpp
|- SortTopN.h
|- Word_Group_Cnt.cpp
|- Word_Group_Cnt.h
|- WordCount.cpp
|- WordCount.h
|- pch.cpp
|- pch.h
|- main.cpp
|- WordCount.vcxproj
|- cvpr
|- Crawler.py
|- result.txt
#### 關系圖
說明算法的關鍵與關鍵實現部分流程圖
#### 單詞權重計算
根據個人項目判斷有效單詞的算法進行改進,講按字符串讀取改成按行讀取,判斷該行是title還是absract,通過存儲分割符的位置進行切割讀取有效詞並計算權重。
#### 詞組切割
根據用戶輸入決定詞組切割長度,切割過程中借用隊列輔助存儲每一個合法單詞的長度,遇到不合法單詞則可以將隊列que清空即可,否則就根據詞組單詞組成長度以及隊列存儲的單詞長度進行進隊出隊操作,達到更新string後進行截取(使用substr庫函數)得到新的詞組存入map容器。
附加題設計與展示
爬蟲能力有限orz,只從網站綜合爬取論文的除題目、摘要外其他信息。爬蟲語言使用python。額外爬取信息有:作者、PDF鏈接、SUPPPDF鏈接、ARXIV鏈接。
txt文件如圖:
想象力有限orz,我們分析了論文列表中各位作者之間的關系,論文A的第一作者可能同時是論文B的第二作者,不同論文多位作者之間可能存在著聯系,並將關系可視化。
我們從附加功能1的txt文件提取了發表在2018cvpr頂會上的所有論文的第一作者和第二作者,使用分析工具NodeXL做出了關系圖譜。
圖譜全貌如下:
逐漸剔除較少聯系點的圖譜如下:
(沒有第二作者時在第二作者處填empty)
可以看出在2018cvpr上發表論文且是主要作者的大神就是——Qi Wu,緊接著還有Wei Wang,Tomer Michaeli,Ross Girshick等等。(然後發表數目多的主要作者們互相之間都不合作的嗎……
關鍵代碼解釋
具體解釋: 使用輔助隊列存儲每個合法單詞的長度,通過入隊和出隊操作更新string進行截取不斷獲得新的詞組;函數詳情在以下.h文件中均有描述;
- 詞組統計.h文件
- 詞頻排序.h文件
- 單詞統計.h文件
/*統計指定長度的合法單詞量形成的詞組詞頻*/
void Word_Group_Cnt(int word_Group_Len, string str, map <string, int > &group_Map,int ttl_Abs)
{
string word_Now = "";
string word_Group = "";
int lenth = str.length();
queue <int> que;
for (int i = 0; i < lenth; i++)
{
if (Is_Num(str[i]) || Is_Engch(str[i]) && i != lenth - 1) //字符是字母或數字就將其連接到word_Now
{
word_Now += str[i];
continue;
}
else if (Is_Num(str[i]) || Is_Engch(str[i]) && i == lenth - 1) //字符是字母或數字且為字段末位連接後就需要對末尾的單詞判斷是否為合法單詞,否則會跳出循環漏掉末尾一個單詞;
{
word_Now += str[i];
word_Now = Is_Word(word_Now);
int word_Len = word_Now.length();
if (word_Len >= 4)
{
word_Group += word_Now;
if (que.size() == word_Group_Len - 1)
{
group_Map[word_Group] += ttl_Abs;
}
else if (que.size() > word_Group_Len - 1)
{
word_Group = word_Group.substr(que.front());
group_Map[word_Group] += ttl_Abs;
}
}
else if (word_Len >= 0 && word_Len < 4)
{
continue;
}
}
else
{
word_Now = Is_Word(word_Now);
int word_Len = word_Now.length();
if (word_Len >= 4)
{
word_Group += word_Now;
if (que.size() < word_Group_Len - 1) //隊列大小比所需合法單詞數word_Group_Len-1小情況
{
word_Group += str[i];
word_Len += 1;
while (!Is_Num(str[i + 1]) && !Is_Engch(str[i + 1]) && i + 1 < lenth)
{
word_Group += str[i + 1];
word_Len += 1;
i += 1;
}
que.push(word_Len);
word_Now = "";
}
else if (que.size() == word_Group_Len - 1) //隊列大小=所需合法單詞數word_Group_Len-1情況
{
group_Map[word_Group] += ttl_Abs;
word_Group += str[i];
word_Len += 1;
while (!Is_Num(str[i + 1]) && !Is_Engch(str[i + 1]) && i + 1 < lenth)
{
word_Group += str[i + 1];
word_Len += 1;
i += 1;
}
que.push(word_Len);
word_Now = "";
}
else if (que.size() > word_Group_Len - 1) //隊列大小等於詞組所需合法單詞數量,則對隊列進行進隊和出隊操作更新string並進行截取
{
word_Group = word_Group.substr(que.front());
group_Map[word_Group] += ttl_Abs;
que.pop();
word_Group += str[i];
word_Len += 1;
while (!Is_Num(str[i + 1]) && !Is_Engch(str[i + 1]) && i + 1 < lenth)
{
word_Group += str[i + 1];
word_Len += 1;
i += 1;
}
que.push(word_Len);
word_Now = "";
}
}
else if (word_Len > 0 && word_Len < 4) //遇到不合法單詞且不是分隔符或空串的,則返回為no,將隊列清空
{
while (que.empty() != 1)
{
que.pop();
}
word_Now = "";
word_Group = "";
}
else if (word_Len == 0)
{
continue;
} //是否要判斷在輸入到函數中
}
}
}
性能分析與改進
描述你改進的思路
在下面所示截圖很容易可以看出主要是詞組詞頻統計的函數消耗的比較多,程序中消耗最大的函數為Cut_Ttl_Abs占50.09%,其次是Word_Group_Cnt函數占43.59%,點進代碼查看消耗比較大的代碼段是由於直接或間接使用Is_Word函數,但是Is_Word函數為每個單詞判定是否合法所必需用到的。且從代碼中可以看出其Is_Word函數中代碼行消耗分布都很平均,所以暫時不知如何進一步優化改進。展示性能分析圖和程序中消耗最大的函數
單元測試
測試的函數及其測試數據構造思路:
LineCount(計算行數):
構造思路:- 中間增加空白行;
- 當字符串長度大小超過1024,txt則會在視覺上顯示兩行但其實是一行;
- 多(十)個測試數據文件防止偶然性
WordCount(計算單詞數):
構造思路:- 多種不合法單詞,如123ahkjk,kkk3jkljk,kkk,lll等等;
- 通過特殊分隔符分隔的一長串單詞視為兩個單詞,如title-task,github--parent等等;
- 標題摘要的兩個關鍵字可能不僅只在首端“Title:”、“Abstract:”形式出現,後續摘要標題內容也可能存在這兩個單詞;
- 多(十)個測試數據文件防止偶然性;
CharCount(計算字符數):
構造思路:- 編號長度不固定,比如1、23、978、976、46,這些字符統計長度會變化;
- 兩篇論文間或者論文列表後面有多余空白行,即不止只有兩行空行時,應該計算其字符數;
- 多(十)個測試數據文件防止偶然性;
IsWord(判斷是否為合法單詞):
構造思路:- 輸入有大寫字母返回時應將其轉化為小寫形式輸出;
- 輸入為多種不合法單詞,如123,123llllk,kkk,kkk1235,llk2lk等等輸出為no;
- 輸入為空串時返回依舊為空串;
Word_Group_Count(詞組切割統計):
構造思路:- 合法單詞之間可能存在多個分隔符;
- 合法單詞之間存在不合法單詞;
- 連續合法單詞個數超過用戶定義組成詞組單詞個數;
- 同一詞組出現多次;
SortTopNum(對前Num單詞或詞組進行詞頻排序):
構造思路:- 不同單詞的數量不同;
- 單詞數量相同但是其字典序不同;
- Num數量變化詞組合法單詞數變化;
對於其他一些函數,因為在計算行數、計算單詞數、計算字符數、詞組切割統計以及詞頻排序中都有調用,而這些函數測試均正確,因此那些簡易的函數的沒有做出測試,通過復雜函數的正確測試間接反應其正確性。
部分代碼展示:
namespace IsWord //判斷是否是單詞
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
string word = "123ajlk";
word = Is_Word(word);
Assert::IsTrue(word == "no");
}
TEST_METHOD(TestMethod2)
{
string word = "Ajlk";
word = Is_Word(word);
Assert::IsTrue(word == "ajlk");
}
TEST_METHOD(TestMethod3)
{
string word = "Ajl";
word = Is_Word(word);
Assert::IsTrue(word == "no");
}
TEST_METHOD(TestMethod4)
{
string word = "Ajlk123";
word = Is_Word(word);
Assert::IsTrue(word == "ajlk123");
}
TEST_METHOD(TestMethod5)
{
string word = "jl12k23";
word = Is_Word(word);
Assert::IsTrue(word == "no");
}
};
}
namespace Word_Group_Count //詞組切割統計
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
map<string, int> m;
Word_Group_Cnt(3, "aaaa bbbb cccc cccc bbbb cccc aaaa dddd cccc bbbb aaaa bbbb cccc dddd", m, 10);
Assert::IsTrue(m["aaaa bbbb cccc"] == 20 && m["bbbb cccc cccc"] == 10);
}
TEST_METHOD(TestMethod2)
{
map<string, int> m;
Word_Group_Cnt(4, "aaaa bbbb cccc dddd bbbb cccc aaaa dddd cccc bbbb aaaa bbbb cccc dddd", m, 10);
Assert::IsTrue(m["aaaa bbbb cccc dddd"] == 20 && m["bbbb cccc dddd bbbb"] == 10);
}
TEST_METHOD(TestMethod3)
{
map<string, int> m;
Word_Group_Cnt(2, "aaaa bbbb cccc cccc bbbb cccc aaaa dddd cccc bbbb aaaa bbbb cccc dddd", m, 10);
Assert::IsTrue(m["bbbb cccc"] == 30 && m["aaaa bbbb"] == 20);
}
TEST_METHOD(TestMethod4)
{
map<string, int> m;
Word_Group_Cnt(5, "aaaa (bbbb) cccc cccc bbbb cccc aaaa dddd cccc bbbb aaaa bbbb cccc dddd", m, 10);
Assert::IsTrue(m["aaaa (bbbb) cccc cccc bbbb"] == 10 && m["bbbb aaaa bbbb cccc dddd"] == 10);
}
TEST_METHOD(TestMethod5)
{
map<string, int> m;
Word_Group_Cnt(6, "aaaa (bbbb)-cccc cccc bbbb cccc aaaa dddd cccc bbbb aaaa bbbb cccc dddd", m, 10);
Assert::IsTrue(m["aaaa (bbbb)-cccc cccc bbbb cccc"] == 10 && m["aaaa dddd cccc bbbb aaaa bbbb"] == 10);
}
};
}
代碼覆蓋率:
貼出Github的代碼簽入記錄
(兩人提交記錄)遇到的代碼模塊異常或結對困難及解決方法
① 佩佩
問題描述:
- (已解決)詞組切割時題意不清楚,對作業的分隔符也要輸出的要求感覺太不友好和助教聯系請教了一段時間,最終敵不過大家都沒意見,本人勢單力薄,而助教說的投票杳無音信,於是,恭喜自己決定有無分隔符的都寫個函數,這樣便不受影響了;
- (已解決)對Title和Abstract進行切割的時候一度以為滿了txt一行所示的1024個字符便算一行,因此切割時做了許多額外的滿1024個字符考慮行與行之間的拼接組成總的abstract字段,但其實getline就是讀取一行遇到換行符才會停下;
- (已解決)單元測試時遇到模塊計算機x86和目標計算機x64沖突的問題,即庫文件32位和項目文件64位之間存在沖突,一直不能正確測試;
做過哪些嘗試:
- 直接需要輸出分隔符和不需要輸出分隔符函數分別寫一個,就不存在這樣的焦慮了,替自己的機智鼓掌!
- 最後在做單元測試的時候出現挺多錯誤,進行函數斷點調試時發現原來getline讀取的string直接就達到1058!(然後感慨於自己的sb,分什麽1024)於是對代碼進行修改刪除;
- 敲重點!!! 琢磨了一個晚上下午下課至晚上8、9點不間斷4 、5個小時查閱眾多資料和尋求大佬未果(在此謝謝暢暢同學的分析和幫忙),最終仔細分析其根源,靈感將至,發現原來是obj文件路徑設置的時候,其實創建一個Visual Studio項目會有x64和Debug和WordCount好幾個文件夾,其中.exe和.obj文件在x64文件夾和Debug文件夾中都有,而我定的路徑是Debug中的(個人看法:其實是32位產生的obj),因此會一直報錯,將路徑修改為x64裏面的obj文件即可;關於這個問題詳情可以參考我記錄的糾錯博客
- 有何收獲:
- 沒有什麽不友好要求,有的只是自己僥幸想方便想偷懶的心理,所有可能要求做一遍就easy soso了;
- 做單元測試真是一件需要時間,十分重要,任務繁瑣的活,個人認為單元測試的目的便在於對於不同的函數進行多種情況覆蓋考慮,測試其正確性,而不僅僅是為了完成單元測似的要求每個函數寫一個簡單的例子測試正確便視作測試完畢;單元測試是可以幫助確保函數的正確性,項目的正確性。
- 對於碰到的bug就應該多問多查多試探多琢磨,你會發現總是能解決的,雖然時間花費的不確定性蠻大(偷笑),這個自然就看手法了;
- 一直以來都覺得為什麽要畫流程圖,流程圖怎麽畫?總覺得很繁瑣和沒有必要,這次從頭畫了一遍流程圖收獲了很多!(當然畫了蠻久)確實流程圖可以幫助一個即使沒有接觸具體代碼的人也能很快的了解實現的思路和框架。並且可以在自己畫的過程中幫助理清代碼的邏輯,對自己寫的代碼更清晰的認識。
②沸沸
- 問題描述:
(已解決)在附加功能的實現上,我們一開始爬到了作者和論文pdf網址等信息就止步於此,對更多信息的抓取和分析上產生了困難。
- 做過哪些嘗試:
- 從google學術上意外發現了作者的個人資料,詳盡到有與作者關系密切的其他作者,我們因此有了直接爬取現有分析結果的想法。失敗的原因有很多,主要原因還是因為技術問題,作為剛剛入門python的小白對google的反爬封鎖實在苦手,還有存在作者重名的問題難以檢索,放棄。
- 嘗試爬蟲軟件,不會用,失敗。
- 爬取作者使用數據分析軟件NodeXL大獲成功,一舉生成關系圖譜。
- 從google學術上意外發現了作者的個人資料,詳盡到有與作者關系密切的其他作者,我們因此有了直接爬取現有分析結果的想法。失敗的原因有很多,主要原因還是因為技術問題,作為剛剛入門python的小白對google的反爬封鎖實在苦手,還有存在作者重名的問題難以檢索,放棄。
有何收獲:
爬蟲能力提升,搜索能力提升評價你的隊友
胡緒佩
值得學習的地方
佩佩,有太多值得我學習的地方了……比如遇到bug不放棄堅持持續打碼n小時解決,比如遇到問題鉆研求知的精神,比如知難而進的性格,比如在我睡覺的時候把博客發了……好隊友!!!
需要改進的地方
基本上沒有,除了讓我不要偷偷背著他打代碼hhhhhhh
莊卉
#### 值得學習的地方
沸沸,有太多值得我學習的地方了+1......比如看到項目便知道體諒隊友把難點(附加功能)攬下力肝(卉:表示並沒有完成得很好),比如寫代碼速度總是莫名其妙超快的不知道有何秘訣!比如善於和隊友溝通交流的團隊精神,解決未知困難的能力plusplus,完美解決了本次作業第一第二作者圖譜的這個難點,比如溫柔美麗的氣質......棒隊友!!!
#### 需要改進的地方
基本上沒有,再有機會緊抱大腿我定緊緊不放xixixi
學習進度條
第N周 | 新增代碼(行) | 累計代碼(行) | 本周學習耗時(小時) | 累計學習耗時(小時) | 重要成長 |
---|---|---|---|---|---|
1 | 666 | 666 | 15 | 15 | 復習c++,學習單元測試和代碼覆蓋率,學習git |
2 | 97 | 763 | 4 | 19 | 沒什麽成長,就是在優化代碼 |
3 | 0 | 0 | 10 | 29 | 閱讀《構建之法》第三章和第八章,學習使用Axure RP8,了解原型設計的方法 |
4 | 197 | 960 | 20 | 49 | 進一步學習爬蟲(了解beautifulsoup使用、學會使用正則),學習使用git進行團隊協作,學習使用NodeXL,了解flask |
18軟工實踐-第五次作業-結對作業2