1. 程式人生 > >個人第二次作業

個人第二次作業

很多 多個 har 如果 我認 center idt 發現 htm

0前言

本次作業要求做一個詞頻統計的軟件,功能簡單來說是實現英文文章的單詞總數,以及每個單詞出現次數的統計。

編程語言語言:C++

工具:CodeBlocks

git地址:https://git.coding.net/Vrocker/wf.git

一、主要功能

0.簡述:

題目要求總共為四部分,通過對這四部分功能需求的研究,可以將主要功能細分成以下部分:

1.可以讀入本地txt格式的文件,要求內容是英文書籍或者文章。

2.統計文件中不重復出現單詞的總數,並輸出。

3.統計文件中英文單詞的詞頻,即出現的單詞以及這個單詞出現的次數,並且降序輸出。

4.要求在命令提示符中直接控制程序,即輸入相應參數實現相應的功能。

5.支持單一文件,同時支持指定文件夾下的所有文件中英文單詞的詞頻統計。

6.在多文件統計時只輸出出現次數最多的十個單詞。

二、設計思路

  要實現題目要求的功能,我首先想到的是用數組或者鏈表來進行操作:

  首先讀入輸入的文本文件,將文本文件的各個字符包括標點符號等逐一放進數組中。然後設置一個指針從頭開始進行遍歷,一旦遇到標點或者空格的時候(我認為“-”連接的兩個或多個英文單詞應該算作一個單詞,所以“-”不用排除掉)就可以分割單詞,將這個單詞保存到另外一個數組中。然後再設置另外一個指針,對新數組進行遍歷,統計每個單詞出現的次數,以及這個單詞本身。最後利用排序算法,例如冒泡排序,進行降序排序。這樣就可以大致實現主要的功能。

  但我在查閱資料的時候,我看到一篇博客名為”【tips】【詞頻統計】中可能用到的資源,以C++為例“的博客,網址是:http://www.cnblogs.com/Z-XML/p/3329234.html(真的很感謝這位作者)。看完這篇博客我了解到在C++中有一個MAP容器,可以更好更簡單,而且更快的完成想要的功能。因為之前接觸C++的時候,並不了解map容器,所以便開始了學習的過程。同時在這篇博客中我了解到利用vector以及自帶的sort函數可以排序數組。這樣就大大減少了工作量。但是由於之前我也沒接觸過這些,所以也要開始學習這些對我來說新的概念和方法。所以我放棄了最開始選擇的設計思路,改用這個來設計我的程序。

三、難點

1.參數

  功能要求通過命令提示符輸入相應的參數可以直接進行操作。這樣的設計方法,之前我並不了解。所以開始查詢資料,找解決方案。因為選擇了C++進行編程,所以也開始看相關的C++書籍。這個功能花費了我相當多的時間來解決。首先的難題是不知道怎麽可以獲取到這些參數,同時也不清楚怎麽樣對參數進行判斷,以便進行不同的操作。

  在學習之後,知道了C++中可以用int main(int argc, char* argv[])定義主函數,從而直接實現參數的功能。部分代碼如下:

 1     for(int i=1; i<argc; i++)//用於接受從命令提示符傳遞進來的參數
 2     {
 3         str = argv[i];
 4         if(str=="-s")//如果從命令提示符傳遞的第一個參數是“-s”執行以下操作
 5         {
 6             char const *a = "g:\\";
 7             char const *b = argv[i+1];
 8             std::string const& cc = std::string(a) + std::string(b);
 9             char const *c = cc.c_str();//將兩個char進行拼接,以便傳入fin.open函數中
10             fin.open(c);
11             break;//跳出判斷
12         }
13         else  if(str=="floder")//如果從命令提示符傳遞的第一個參數是floder執行以下操作
14         {
15             system("dir /b /a-d G:\\floder\\*.* >d:\\allfiles.txt");//讀取指定文件夾下的所有txt文件
16             for(int j=2; j<argc; j++)//將所有的txt文件寫入流文件
17             {
18                ............//此功能未能實現
19             }
20         }
21         else//當傳遞的第一個參數是txt文件的文件名時
22         {
23             char const *a = "g:\\";
24             char const *b = argv[i];
25             char const *d = ".txt";
26             std::string const& cc = std::string(a) + std::string(b) + std::string(d);
27             char const *c = cc.c_str();
28             fin.open(c);
29         }
30     }    

2.拼接char*

  要求的功能中,要實現在命令提示符輸入wf和書名來實現詞頻統計。而在我設計的程序中,要用到被統計文件的實際地址。例如"c:\\a.txt"。但是文件的名字又是不確定的所以我在這裏要用到拼接char*來實現。在查詢大量資料後,發現可以實現。代碼如下:

1         if(str=="-s")//如果從命令提示符傳遞的第一個參數是“-s”執行以下操作
2         {
3             char const *a = "g:\\";
4             char const *b = argv[i+1];
5             std::string const& cc = std::string(a) + std::string(b);
6             char const *c = cc.c_str();//將兩個char進行拼接,以便傳入fin.open函數中
7             fin.open(c);
8             break;//跳出判斷
9         }

3.去除標點

  要實現統計詞頻的功能,標點就一定會影響統計。所以要除去標點,才能正確的統計單詞,並且最後完整正確的輸出。為此定義了一個函數,來實現這個功能。代碼如下:

1 void StringToLower(string &theString)//將字符串轉化成全小寫
2 {
3     int nLen = theString.length();
4     for(int i = 0; i < nLen; i++)
5     {
6         theString[i] = tolower(theString[i]);
7     }
8 }

  並且在輸入文本之後,調用這個函數,將不相關的符號排除。代碼如下:

1     set<char> ignoreSet;//除去標點符號或者會影響判斷單詞等符號
2     ignoreSet.insert(,);
3     ignoreSet.insert(.);
4     ignoreSet.insert(?);
5     ignoreSet.insert(:);
6     ignoreSet.insert(!);
7     ignoreSet.insert(;);
8     ignoreSet.insert(\‘);
9     ignoreSet.insert(");

4.轉換大小寫

  我在第一次測試程序的時候,發現自己犯了一個錯誤。就是大小寫沒有轉換,導致同樣的單詞只是大小寫不一樣,會有兩個結果。這顯然不符合題目的要求。所以為了讓大小寫統一,這裏把統計到的所有單詞都轉化成小寫字母。這樣就不會有問題。代碼如下:

1 void StringToLower(string &theString)//將字符串轉化成全小寫
2 {
3     int nLen = theString.length();
4     for(int i = 0; i < nLen; i++)
5     {
6         theString[i] = tolower(theString[i]);
7     }
8 }

5.排序輸出

  最後要對統計過的單詞以及出現的次數,以次數為標準降序排列輸出。這裏就用到了排序的函數。最開始,我想用冒泡排序或者其他相應的算法進行。但是在了解C++中各種排序的方法之後。我了解到C++自帶一個sort函數可以用於排序。所以在這裏我選擇這個方法。在看了一些用map容器最後進行排序的相關代碼之後,我發現這些代碼最後大部分都是把map轉化成vector之後再進行排序。但是對於這個知識點我之前也是沒有接觸過。所以再進行學習之後,模仿同樣方法的排序代碼嘗試進行自己編寫。代碼如下:

 1     struct CmpByValue
 2 {
 3     bool operator()(const PAIR& lhs, const PAIR& rhs)
 4     {
 5         return lhs.second > rhs.second;
 6     }
 7 };
 8 ..................
 9     vector<PAIR> wmap_vec(wmap.begin(), wmap.end());
10     sort(wmap_vec.begin(), wmap_vec.end(), CmpByValue());

  利用wmap_vec.size()可以直接輸出總數。

  這時候每個單詞存放在對應的wmap_vec[i].first 中。而出現的次數存放在對應的wmap_vec[i].second中。再用一個for循環就可以輸出。

 1     float sum=0;
 2     for (int i = 0; i != wmap_vec.size(); ++i)//利用map輸出總字數
 3     {
 4         sum=sum+1;
 5     }
 6     cout<<"total    "<<sum<<"  words"<<endl<<endl<<endl;
 7     for (int i = 0; i != wmap_vec.size(); ++i)//利用map輸出每個單詞以及其出現次數
 8     {
 9         cout << left << setw(50) << wmap_vec[i].first << wmap_vec[i].second << endl;
10     }

四、功能測試

1.

技術分享

2.

技術分享

3.

  這個部分支持命令行輸入存儲有英文作品文件的目錄名,批量統計。這個功能花費了我很長的時間來做。但是還是沒有做好。試了很多方法,但是都沒有成功。上述代碼有出現str = argv[i];else if(str=="floder");判斷參數成功的時候試過設置一個flag,當主函數運行到輸出部分的時,如果flag正確則多次輸出結果。但是沒有成功。技術分享

五、總結

  剛開始我看到這個題目的時候,第一感覺是挺簡單的。邏輯不復雜,而且應該比較容易實現。所以一直沒覺得會花費我太多時間。但當我真正開始著手寫代碼的時候發現,可以用更簡單便捷的方法實現。所以我就開始研究這個新的方法。因為覺得代碼量並不大,所以又一次估算時間應該沒有多久。然而,寫著寫著我開始發現自己的知識掌握的太不牢固,同時也發現自己想要用的這個方法裏面,有太多東西是我之前沒有接觸過的。但是既然選擇了這個方法,況且這個方法看起來確實比較簡潔,我就硬著頭皮邊寫邊學。

  首先map容器和vector之前我並不知道。所以兩個知識點我花了相當大的時間來處理。之後參數的相關知識也是一知半解,再查了很多資料之後才開始著手編寫,這個環節花費的時間也不少。雖然知識點不多,但是由於沒有實際編寫過,所以在調試,測試的過程中出現了很多很多的問題。同時,char*的拼裝,也是在查閱資料之後在知乎看到一個回答,才讓我很好的解決。這些只是花費時間比較多的環節,實際上每個步驟我都花費了相當長的時間。總結來看,預估的時間遠遠小於我真實花費的時間。

  通過這次編寫程序,讓我最感觸的就是預估的時間可能會遠遠低於花費的時間。一個看上去不難的程序,在編寫的過程中也會遇到很多難題。同時,這次編寫程序讓我開始知道自己掌握的知識點,遠遠不夠,需要不斷的學習同時不斷地練習才能保證熟練的掌握一門語言。

PSP表格

分類

預計時間(分鐘)

實際時間(分鐘)

實際總時間(分鐘)

時間差(分鐘)

功能一

90

95

155

65

測試1

60

功能二

150

150

240

90

測試2

90

功能三

120

240

330

110

測試3

90

功能四

180

90

150

-30

測試4

60

總體調試

60

90

90

30

個人第二次作業