【現代軟件工程】第一次作業——詞頻統計
目錄
1.1基本功能
1.2設計實現
1.3代碼結構
1.4測試運行
1.5性能分析
1.6項目總結
1.7 PSP展示
1.1 基本功能
1. 統計文件的字符數(只需要統計Ascii碼,漢字不用考慮,換行符不用考慮,‘\0‘不用考慮)(ascii碼大小在[32,126]之間)
2. 統計文件的單詞總數
3. 統計文件的總行數(任何字符構成的行,都需要統計)(不要只看換行符的數量,要小心最後一行沒有換行符的情形)(空行算一行)
4. 統計文件中各單詞的出現次數,輸出頻率最高的10個。
5. 對給定文件夾及其遞歸子文件夾下的所有文件進行統計
6. 統計兩個單詞(詞組)在一起的頻率,輸出頻率最高的前10個。
7. 在Linux系統下,進行性能分析,過程寫到blog中(附加題)
關於字符、行數與單詞的統計規則詳情請見:http://www.cnblogs.com/denghp83/p/8627840.html
1.2 設計實現
1.2.1 解題思路
1. 用命令行參數輸入文件路徑,判斷其是單個文件還是文件夾。
若為單個文件則直接打開,統計字符、單詞與行數;若為文件夾,則遞歸遍歷該文件夾下的所有文件進行統計。
之前用文件操作用得較少,所以對遍歷文件夾的操作不熟悉,在網上找了點資料。
(學習筆記:
1)存儲文件各種信息的結構體中:unsigned attrib表示文件的屬性,
2)_findfirst函數
long _findfirst( char*filespec,struct _finddata_t *fileinfo );
返回值:如果查找成功的話,將返回一個long型的唯一的查找用的句柄(就是一個唯一編號)。這個句柄將在_findnext函數中被使用。若失敗,則返回-1。
fileinfo :這裏就是用來存放文件信息的結構體的指針。這個結構體必須在調用此函數前聲明,不過不用初始化,只要分配了內存空間就可以了。函數成功後,函數會把找到的文件的信息放入這個結構體中。
3)_findnext函數
int _findnext( long handle, struct_finddata_t *fileinfo );
返回值:若成功返回0,否則返回-1。
參數:handle:即由_findfirst函數返回回來的句柄。
fileinfo:文件信息結構體的指針。找到文件後,函數將該文件信息放入此結構體中。
https://blog.csdn.net/aoshilang2249/article/details/37819159)
2. 對於字符、行數與單詞數統計,將字符數、行數與單詞數作為全局變量,一開始最簡單的想法是分三次讀取文件,後來想想真的太費時間了。
最後的方案是每讀一個文件,就把字符、行數與單詞數統計好。
3. 對於單詞和詞組頻率的統計:創建兩個哈希表,使用ELFHASH哈希算法計算索引值,使用拉鏈法處理沖突。
對詞組頻率的統計一開始沒有什麽頭緒,後來翻看了其他同學的博客,通過操作前後兩個單詞的結構體指針來實現詞組在哈希表中的存儲,才大概有了點頭緒。
4. 輸出頻率前十的單詞和詞組:通過遍歷哈希表實現。
1.2.2 實現細節
單詞和詞組的結構體:
typedef struct wordnode { int times; char word[MAX];//單詞原型 char wordhash[MAX];//去掉最末尾數字且字母全為小寫 struct wordnode *next; }wordnode, *wordlist; typedef struct phrasenode { int times; wordlist wordpre;//前一個單詞 wordlist wordaft;//後一個單詞 struct phrasenode *next; }phrasenode, *phraselist;
1. 字符數統計:ASCII碼值在32-126之間的字符,則字符數加一。
2. 行數統計:掃描到‘\n‘,則行數加一。每個文件掃描結束,行數再加一。(自我感覺這個統計方法有點不太靠譜。)
3. 單詞數統計:當掃描到分隔符後的第一個字母或數字時,開始將該字符存儲到緩沖數組,直到遇到下一個分隔符。
再對緩沖數組中的字符串做分析,如果長度(不含末尾‘\0‘)大於等於4並且前四個字符都為字母,則單詞數加一。
為了便於計算哈希算法的鍵值和處理沖突,將字符串做一些處理,去掉最末尾的數字並將剩下的均轉化為小寫。
根據鍵值與字符串的大小比較在哈希表中查找,若查找失敗,則創建一個新結點;若查找成功,則次數加一。
4. 詞組數統計:按上述方法記錄一下每一個單詞的結構體指針。
若不是該文件的第一個單詞,將它與上一個單詞合在一起生成一個哈希鍵值,用與統計詞頻相似的方法處理哈希表。
for (i = 0; ((ch >= ‘a‘&&ch <= ‘z‘) || (ch >= ‘A‘&&ch <= ‘Z‘) || (ch >= ‘0‘&&ch <= ‘9‘)) && ch != EOF; i++) { charactercount++; buffer[i] = ch; ch = fgetc(fp); } charactercount--; buffer[i] = ‘\0‘; if (i >= 4 && ((buffer[0] >= ‘a‘&&buffer[0] <= ‘z‘) || (buffer[0] >= ‘A‘&&buffer[0] <= ‘Z‘)) && ((buffer[1] >= ‘a‘&&buffer[1] <= ‘z‘) || (buffer[1] >= ‘A‘&&buffer[1] <= ‘Z‘)) && ((buffer[2] >= ‘a‘&&buffer[2] <= ‘z‘) || (buffer[2] >= ‘A‘&&buffer[2] <= ‘Z‘)) && ((buffer[3] >= ‘a‘&&buffer[3] <= ‘z‘) || (buffer[3] >= ‘A‘&&buffer[3] <= ‘Z‘))) { wordtotal++;//此時i即為單詞原始長度 for (k = i - 1; ; k--) { if ((buffer[k] >= ‘a‘&&buffer[k] <= ‘z‘) || (buffer[k] >= ‘A‘&&buffer[k] <= ‘Z‘)) break;//k represents the last location of a character } //my_strlwr(regular, buffer, k + 1); current = wordFrequency(buffer, k + 1); if (wordtotal > 0) { //不是第一個單詞 phraseFrequency(last, current); } last = current; }
1.3 代碼結構
詳細代碼地址:https://github.com/EstherXr/learngit/blob/master/homework1.cpp
1.4 測試運行
1. 助教給的測試集
上面為我的結果,下面為助教給的測試結果。
頻率前十的單詞和詞組及頻率與助教的結果相同,但是字符數、行數與單詞數都有偏差。對於單詞數,我覺得是各人的定義不同,比如ab123abcd中的abcd到底算不算單詞。
2. 空文件:
3. 遍歷文件夾測試一:
4. 遍歷文件夾測試二:
5. 單文件輸出所有單詞:
1.5 性能分析
CPU總使用情況
遍歷文件夾函數:
統計字符數、行數與單詞數:
分析:
從函數的CPU使用情況來看,大部分時間都花費在遍歷文件夾與統計函數上。
1.6 項目總結
到真正寫代碼和做東西的時候,就會發現自己會的東西真的太少了。(所以這次基本是用純C寫的)也因為之前寫代碼寫得太少了,所以對自己能力的估計也很不準確,導致規劃的效率很低。
在交代碼的那天晚上才開始做移植,但是在Linux系統上測試一直有問題,所以最後只好交了一份沒有移植的代碼。之後要把這個問題搞明白,也要開始學習如何使用虛擬機。
以後做項目要多些文檔,可以幫助自己梳理思路,更有條理。這也是這次作業不足的地方。
最後,一定要和身邊的人多交流。
1.7 PSP展示
預估耗時/min | 實際耗時/min | ||
Planning | 計劃 | 30 | 45 |
-Estimate | -估計這個任務需要多少時間 | 30 | 45 |
Development | 開發 | 1220 | 1600 |
-Analysis | -需求分析 | 120 | 60 |
-Design Spec | -設計文檔 | 90 | 60 |
-Design Review | -設計復審 | 30 | 20 |
-Coding Standard | -代碼規範 | 20 | 20 |
-Design | -具體設計 | 120 | 240 |
-Coding | -具體編碼 | 600 | 900 |
-Code Review | -代碼復審 | 60 | 60 |
-Test | -測試 | 180 | 240 |
Reporting | 報告 | 180 | 265 |
-Test Report | -測試報告 | 90 | 180 |
-Size Measurement | -計算工作量 | 60 | 40 |
-Postmortem | -總結反思 | 30 | 45 |
1430 | 1910 |
【現代軟件工程】第一次作業——詞頻統計