軟工1816 · 第五次作業 - 結對作業2
一、Github地址 課程項目要求 隊友博客
二、具體分工
- 031602225 林煌偉 :負責C++部分主要功能函數的編寫,算法的設計以及改進優化
- 031602230 盧愷翔 : 爬蟲實現以及附加功能,代碼框架設計,接口封裝
三、psp表格 & 學習進度條
- psp表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 60 |
· Estimate | · 估計這個任務需要多少時間 | 30 | 30 |
Development | 開發 | 1000 | 1160 |
· Analysis | · 需求分析 (包括學習新技術) | 120 | 180 |
· Design Spec | · 生成設計文檔 | 10 | 10 |
· Design Review | · 設計復審 | 10 | 10 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 10 | 10 |
· Design | · 具體設計 | 30 | 30 |
· Coding | · 具體編碼 | 780 | 900 |
· Code Review | · 代碼復審 | 20 | 20 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 20 | 20 |
Reporting | 報告 | 130 | 130 |
· Test Repor | · 測試報告 | 60 | 60 |
· Size Measurement | · 計算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 60 | 60 |
合計 | 1190 | 1350 |
- 學習進度條
第N周 | 新增代碼(行) | 累計代碼(行) | 本周學習耗時(小時) | 累計學習耗時(小時) | 重要成長 |
---|---|---|---|---|---|
1 | 500 | 500 | 12 | 12 | 單元測試的編寫 |
2 | 0 | 500 | 10 | 22 | Axure原型設計工具的使用 |
3 | 500 | 1000 | 10 | 32 | c++算法設計編寫能力,Debug調試能力 |
四、解題思路描述與設計實現說明 & 關鍵代碼解釋
爬蟲使用
Attention!
爬蟲的CVPR.py
文件放在CVPR\_Python
的根目錄下。CVPR.exe
文件放在
CVPR\_Python/dist/CVPR/CVPR.exe
本次我們使用Python編寫爬蟲,利用requests
庫獲取網頁的html標簽樹,並用BeautifulSoup
庫對其進行解析。利用正則表達式或者BeautifulSoup
類對象獲取我們想要的信息。
#encoding: utf-8
import time
import requests
import multiprocessing
from multiprocessing import Queue,Process,Pool
from bs4 import BeautifulSoup
import re
import os
url = ‘http://openaccess.thecvf.com/CVPR2018.py‘
i = 0
href = Queue()
def get_url():
try:
r = requests.get(url,headers=req_header)
r.raise_for_status()
r.encoding = r.status_code
bsObj = BeautifulSoup(r.text,‘lxml‘)
for dt in bsObj.find_all(‘dt‘):
new_url = ‘http://openaccess.thecvf.com/‘ + dt.a[‘href‘]
href.put(new_url)
except:
print("error")
通過上述函數來獲取CVPR官網上所有的論文鏈接,並將他們放在一個隊列中。每次獲取論文的詳細信息時,就從隊列中拿出一個鏈接進行分析。
def get_message(myurl):
try:
global i
title, abstract, authors, pdflink = get_abstract(myurl)
with open(‘result.txt‘,‘a+‘,encoding=‘utf-8‘) as f:
f.write(str(i) + ‘\n‘)
f.write(‘Title: ‘ + title + ‘\n‘)
f.write(‘Authors: ‘ + authors + ‘\n‘)
f.write(‘Abstract: ‘ + abstract + ‘\n‘)
f.write(‘PDF_LINK: ‘+ pdflink + ‘\n‘)
f.write(‘\n\n‘)
f.close()
i += 1
print("%s is crawled!" % myurl)
except:
print(‘error‘)
def get_abstract(newurl):
try:
r = requests.get(newurl,headers=req_header)
r.raise_for_status()
bsObj = BeautifulSoup(r.text,‘lxml‘)
pattern = re.compile(‘(.*)</div>‘)
title = re.findall(pattern, r.text)
title = bsObj.find_all(‘div‘,{‘id‘:‘papertitle‘})[0].text
abstract = bsObj.find_all(‘div‘,{‘id‘:‘abstract‘})[0].text
pattern = re.compile(‘.*<i>(.*)</i>‘)
authors = re.findall(pattern, r.text)[0]
pattern = re.compile(‘.*<a href="(.*)">pdf‘)
pdflink = re.findall(pattern,r.text)[0].replace(‘../..‘,‘http://openaccess.thecvf.com‘)
return title.replace(‘\n‘,‘‘), abstract.replace(‘\n‘,‘‘), authors, pdflink
except:
print("error")
利用正則表達式或BeautifulSoup
類對象來獲取網頁中我們需要的信息,如論文標題、作者、簡介和論文的pdf鏈接。並將他們按照要求格式輸入至result.txt
之中。
if __name__ == ‘__main__‘:
if os.path.exists(‘result.txt‘):
os.remove(‘result.txt‘)
get_url()
start = time.time()
while not href.empty():
get_message(href.get())
# process = []
# num_cpus = multiprocessing.cpu_count()
# print(‘將會啟動進程數為:‘, num_cpus)
# while not href.empty():
# process.append(href.get())
# p = Pool()
# p.map(get_message, process)
# p.close()
# p.join()
end = time.time()
print("共計用時%.4f秒" %(end-start))
本來這裏我想要用多進程來完成本次的爬蟲,但是遇到2個問題,所以放棄了。
- 在使用多進程的時候,我的論文編號會保持在1個數字不動,直到所有進程全部完成,才會將論文編號+1 。
- 在使用pyinstaller對.py文件編譯後的.exe文件,他會無限制地占用CPU資源,使得電腦死機。
希望如果懂得這些原理的大佬能夠幫忙評論一下ORZ
代碼組織與內部實現設計(類圖)
說明算法的關鍵與關鍵實現部分流程圖
- 針對論文的格式,遍歷一遍,提取出要求統計的字符,即 Title: 和 Abstract: 後面的所有字符;並且分成兩個字符串string title 和 string abstract,以方便後面的權重分開計算
void MyCount::TitleAndAbstract(string filename)
{
char ch;
ifstream fin;
fin.open(filename);//打開文件
if (!fin)
{
cout << "can not open file" << endl;
}
while (!fin.eof())
{
ch = fin.get();
if (ch == ‘\n‘)
{
ch = fin.get();
if (ch == ‘T‘)
{
for (int i = 0; i <= 6; i++)
ch = fin.get();
while (ch != ‘\n‘)
{
title_str += ch;
ch = fin.get();
}
title_str += ch;
}
}
if (ch == ‘\n‘)
{
ch = fin.get();
if (ch == ‘A‘)
{
for (int i = 0; i < 10; i++)
ch = fin.get();
while (ch != ‘\n‘ && !fin.eof())
{
abstract_str += ch;
ch = fin.get();
}
if (ch == ‘\n‘)
abstract_str += ch;
}
}
}
fin.close();
}
2.統計有效行數,字符數,以及單詞數的算法思路和個人項目的相同,只不過給定的參數從文件名變成 title + abstract 的字符串,因為要去掉論文格式中不需要統計的部分;具體詳見詞頻統計作業
3.詞組統計思路:
- 將title字符串與abstract字符串傳入函數,各自遍歷一遍,識別出 單詞+分隔符 的格式存入一個string數組 ci[] 中,並且換行符單獨存;
for (int i = 0; i < le; i++)//存入所有的詞和字符
{
if ((u[i] >= ‘a‘&&u[i] <= ‘z‘) || (u[i] >= ‘A‘&&u[i] <= ‘Z‘) || (u[i] >= ‘0‘&&u[i] <= ‘9‘))//第一個是字母或數字
{
while ((u[i] >= ‘a‘&&u[i] <= ‘z‘) || (u[i] >= ‘A‘&&u[i] <= ‘Z‘) || (u[i] >= ‘0‘&&u[i] <= ‘9‘))
{
if (u[i] >= ‘A‘&&u[i] <= ‘Z‘)
u[i] += 32;
ci[k] += u[i];
i++;
}
while (!(u[i] >= ‘a‘&&u[i] <= ‘z‘) && !(u[i] >= ‘A‘&&u[i] <= ‘Z‘) && !(u[i] >= ‘0‘&&u[i] <= ‘9‘) && u[i] != ‘\n‘&&i < le)
{
ci[k] += u[i];
i++;
}
if (u[i] == ‘\n‘)ci[++k] = ‘\n‘;//換行符單獨存
i--;
k++;
}
else continue;
}
- 從頭開始遍歷 ci[] 組,以m為個數將符合單詞規範的詞連接起來,存入一個臨時字符串word中,如m=3,則判斷ci[0],ci[1],ci[2]是否為 合法單詞+分隔符,是的話存入word,不是的話跳到該詞的下一個繼續遍歷;
for (int i = 0; i < k; i++)
{
int flag1 = 0;
int flag2 = 0;
for (int j = 0; j < 4; j++)
{
if (!(ci[i][j] >= ‘a‘&& ci[i][j] <= ‘z‘))
{
flag2 = 1;//判斷第一個是否符合單詞規範
break;
}
}
if (flag2 == 0)//第一個符合
{
strcpy_s(temp_cizu.word, 500,ci[i].c_str()); //
temp_cizu.count = 1;
if (w == 1)temp_cizu.count += 9;
for (int j = 1; j < m; j++)
{
int flag3 = 0;
for (int f = 0; f < 4; f++)
{
if (!(ci[i + j][f] >= ‘a‘&& ci[i + j][f] <= ‘z‘))
{
flag3 = 1;//判斷後幾個是否符合單詞規範
i = i + j;
break;
}
}
if (flag3 == 0)//後幾個都合法
strcat_s(temp_cizu.word, 500, ci[i + j].c_str());//
else { flag1 = 1; break; }
}
- 將該臨時字符串word的結尾的分隔符去掉,就是一個合法的需要統計的詞組 ,拿去與合法詞組一一對比,相同的權重個數(位於Title:中的權重加10)累加,都不相同則新增詞組;
if (flag1 == 0) //插入
{
int a = strlen(temp_cizu.word) - 1;//去掉結尾的符號
while (!(temp_cizu.word[a] >= ‘a‘&&temp_cizu.word[a] <= ‘z‘) && !(temp_cizu.word[a] >= ‘0‘&&temp_cizu.word[a] <= ‘9‘))
{
temp_cizu.word[a]=‘\0‘;//
a--;
}
int h, j = t;
for (h = 0; h < j; h++)//相同的詞個數累加
{
if (strcmp(temp_cizu.word,cizu[h].word) == 0)//
{
cizu[h].count++;
if (w == 1)cizu[h].count += 9;
break;
}
}
if (t == 0 || h == j)//新詞生成新的空間
{
cizu[t] = temp_cizu;
t++;
}
}
- 將合法的詞組放入優先隊列中,自定義以頻率數統計,相同的按字典序排列, 輸出前n個詞組及權重值
bool operator< (wo a, wo b)//自定義排序
{
//個數相同的單詞按字典排序
if (a.count == b.count)
{
int i = -1;
do {
i++;
} while (a.word[i] == b.word[i]);
return a.word[i] > b.word[i];
}
//出現頻率排序
else return a.count < b.count;
}
算法思路圖如下:
4.接口參數
在命令行參數中如果遇到-i
則argv[i+1]
是輸入文件名input_filename
,同理-o
對應的輸出文件名output_filename
、-w
權重參數weight
、-m
詞組詞頻統計參數m
、-n
詞頻統計輸出參數n
stringstream ss;
string input_filename, output_filename;
int weight = 0;
int m = 1, n = 10;
for (int i = 1; i < argc; i++)
{
string s;
s = argv[i];
if (s == "-i")input_filename = argv[i + 1];
if (s == "-o")output_filename = argv[i + 1];
if (s == "-m")
{
s = argv[i + 1];
stringstream ss(s);
ss >> m;
}
if (s == "-n")
{
s = argv[i + 1];
stringstream ss(s);
ss >> n;
}
if (s == "-w")
{
s = argv[i + 1];
stringstream ss(s);
ss >> weight;
}
}
五、附加題設計與展示
- 將論文中的作者和pdf鏈接爬取到result.txt中
六、性能分析與改進
用CVPR2018的979篇paper作為輸入數據,參數 -i 1.txt -m 3 -n 10 -w 1 -o output.txt
運行完發現耗時四分鐘多!我的天!調出查看性能分析:
發現主要的耗時問題:兩個string類型對比是否相同耗時達84%,想到之前個人項目的算法中是比較兩個char數組,耗時很少,於是將string類型改為 char數組類型,運行完發現只需20秒
結果如下:
分析報告如下:
最耗時的是count函數,不過改進之後縮短了很多時間
利用插件OpenCppCoverage,查看代碼覆蓋率:高達99%
七、單元測試
部分代碼如下:
#include "stdafx.h"
#include "CppUnitTest.h"
#include "../WordCount/CountAscii.h"
#include "../WordCount/CountWords.h"
#include "../WordCount/File.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace CountAsciiTest //用於對字符數量的統計
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此輸入測試代碼
Ascii test_ascii;
int count = test_ascii.countAscii("countascii.txt");
Assert::IsTrue(count == 28);
}
};
}
namespace CountLineTest //用於測試對空白行的技術
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此輸入測試代碼
Ascii test_ascii;
int count = test_ascii.countLine("countline.txt");
Assert::IsTrue(count == 0);
}
};
}
namespace FileTest //用於對空文件名的測試
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
// TODO: 在此輸入測試代碼
Ascii test_ascii;
int count = test_ascii.countAscii("1.txt");
Assert::IsTrue(count == 0);
}
};
}
八、Github的代碼簽入記錄
九、遇到的代碼模塊異常或結對困難及解決方法
遇到的主要問題還是詞組統計的功能實現,為了確保正確率,花了挺多的時間設計算法,在草稿紙上寫了很多遍思路及其實現代碼
最後成功實現功能還是挺有成就感的,收獲就是鍛煉了自己的代碼編寫能力和算法優化能力,體會到了結對編程的過程,知道了怎麽提高合作的效率,怎麽更好的分工與整合。
十、評價你的隊友
對盧愷翔的評價:
由於是舍友,所以討論起來很方便,有什麽問題提出來都能及時得到解決,合作的效率非常高值得學習的地方
太多了,比如對代碼要求嚴謹規範,我寫的有點亂,參數命名不合理,遭到吐槽,都是靠隊友後期規範化,向他學習以後也盡量寫得規範些需要改進的地方
基本沒有
軟工1816 · 第五次作業 - 結對作業2