1. 程式人生 > >第一次工作報告

第一次工作報告

寫到 時間 lin 取模 加強 避免 關於 函數調用 實現

要求

1. 對源文件(*.txt,*.cpp,*.h,*.cs,*.html,*.js,*.java,*.py,*.php等)統計字符數、單詞數、行數、詞頻,統計結果以指定格式輸出到默認文件中,以及其他擴展功能,並能夠快速地處理多個文件。

2. 使用性能測試工具進行分析,找到性能的瓶頸並改進

3. 對代碼進行質量分析,消除所有警告

4. 設計10個測試樣例用於測試,確保程序正常運行(例如:空文件,只包含一個詞的文件,只有一行的文件,典型文件等等)

5. 使用Github進行代碼管理

6. 撰寫博客

基本功能

1. 統計文件的字符數

2. 統計文件的單詞總數

3. 統計文件的總行數

4. 統計文件中各單詞的出現次數

5. 對給定文件夾及其遞歸子文件夾下的所有文件進行統計

6. 統計兩個單詞(詞組)在一起的頻率,輸出頻率最高的前10個。

7. 在Linux系統下,進行性能分析,過程寫到blog中(附加題)

PSP

技術分享圖片技術分享圖片

前期的分析:

首先看到了題目就立刻想到了會有巨大的數據需要進行處理,自然就會聯想應該如何去解決數據的存放問題與查找問題,從功能需求中一步步想到了使用哈希表對此進行存儲,於是便有了初步的想法:

1、打開一個文件,讀取文件內容;

2、將讀取的信息進行處理;

3、構造一個哈希函數,創建一個哈希表。

之後有關測試文件出來了,便遇到了一個很棘手的問題:對於文件的遍歷,這個起初是打算用C語言中的fopen函數,但仔細一分析發現並不能實現具體的要求,於是便尋求一個可以遍歷文件的操作,在查閱資料後找到了C++語言中的findfirst和findnext函數可以實現文件操作,於是便開始對其的學習,由於之前未接觸過C++,對於其中過程的了解耗費了很長的時間,於是便對測試文件進行了初步的遍歷實驗,起初的結果很不近人意,只能遍歷文件的第一個子目錄裏的文件,無法進入子文件夾,進過判定條件的修改,最終實現了所有文件的遍歷。

此刻第一步便達到了目的,於是開始了文件讀取問題的思考,找到了兩個函數,get和getline函數,首先get函數可以很好的讀取文件中的字符,依靠對文件是否結束的判定可以持續讀取,這個的好處就在於可以無所謂文件中全部的字符總量,可以一邊讀一邊根據判定條件進行對單詞與詞組的操作,並且可以按照換行符的數量判斷一個文件中的行數,這個可以說是很理想;其次是getline函數,它可以一次讀取一行,這個可以省去單獨統計換行符的工作,每次調用的時候就可以進行行數的累加,然後可以直接用一行一行的處理單詞與詞組,這個可以更加模塊化,但是缺點便是文件中會出現一行中有數十萬的字符,就會使得數組溢出導致失敗;經過考慮,我選擇了getline函數。

然後就是開始對這個項目進行框架的搭建,從字符、單詞到詞組,每一個都單獨進行搭建,互不相關,為了達到這個目的,分別為單詞與詞組設計了各自的哈希表,由於要同時對出現頻率的統計,決定構造一個結構體數組,裏面存儲字符串與整型數據,而數組的地址則用設計的哈希函數進行計算。

代碼設計:

1、 文件遍歷

利用已經試驗過的findfirst與findnext函數進行操作;

2、 文件讀取

利用ifstream函數打開文件,再利用getline函數對文件內容進行讀取,在文件函數中直接調用字符的函數、單詞的函數、詞組的函數;

3、 字符統計

對傳入的字符數組,進行遍歷,並隨之進行數據的統計;

4、 單詞統計

對傳入的字符數組進行有條件的遍歷,篩選出符合條件的單詞,調用單詞的哈希表構造函數

(1) 單詞哈希表構造

消除大小寫的影響,即在計算哈希函數時使用單詞前四個字母時,全部化為小寫進行求解,利用平方取中法構造哈希函數,利用開放定址發解決沖突,其中需要調用單詞比較函數和單詞優先級比較函數;

1) 單詞比較函數

比較新單詞與哈希表中同一位置單詞是否為相同單詞;

2)單詞優先比較函數

比較兩個相同單詞在字典輸出的情況下的先後次序;

5、 詞組統計

對傳入的字符數組進行有條件的遍歷,篩選出符合條件的詞組,調用詞組的哈希表構造函數 ,其中判斷詞組需要大量的判定條件;

(1) 詞組哈希表構造

消除大小寫的影響,即在計算哈希函數時使用第一個單詞與第二個單詞各前四個字母時,全部化為小寫進行求解,利用平方取中法構造哈希函數,利用開放定址發解決沖突,其中需要調用詞組比較函數和詞組優先級比較函數;

1) 詞組比較函數

比較新詞組與哈希表中同一位置詞組是否為相同單詞;

2) 單詞優先比較函數

比較兩個相同詞組在字典輸出的情況下的先後次序;

6、 排序

利用冒泡排序法對哈希結構體進行排序,將前十出現頻率的結構體返回主函數

7、文件輸出

利用ofstream函數進行reasult.txt文件的構造;

具體編碼:

1、 文件遍歷函數:

利用findfirst函數與findnext函數,不斷利用文件地址對文件進行遍歷,在遍歷的同時將地址字符串傳入到文件讀取子函數中;

2、 文件讀取函數:

在文件遍歷中調用此函數,利用傳入的地址和ifstream函數對文件進行打開操作,設置一個字符數組,再用getline函數將文件中的數據傳入數組中,由於getline是讀取文件的一行,此時便可以記錄行數的變化,當生成一個數組時便調用字符統計子函數並返回一個值代表字符數,調用單詞統計子函數並返回一個單詞數,調用詞組統計子函數不返回值。

3、 字符統計函數:

判斷從文件讀取子函數傳入的字符數組中的字符,並計數

4、 單詞統計函數:

從字符數組的首地址開始依次判斷,符合單詞的條件便調用單詞哈希構造子函數;

(1)單詞哈希構造函數:

利用單詞的前四位相同的條件,將其ASCLL碼值進行求和,再平方取模,得到的便是存入的地址,為了同時統計單詞的個數,構造了結構體數組,在結構體中存入單詞與頻率,在存入哈希表中時,如果地址處已存單詞,需要進行相同性的比較與優先級的比較,於是調用單詞比較子函數與單詞優先比較子函數;

(2)單詞比較函數:

比較傳入的兩個單詞,依次字符進行比較,遇見不同地方便進行判斷,如果相同字符數小於4個,則返回不同,如果大於4,則判斷之後是否全是數字;

(3)單詞優先比較函數:

此函數在單詞比較之後調用,此時傳入的兩個單詞均為相同,則先判斷相同字符的優先,如果相同則判斷不同的優先;

5、 詞組統計函數:

為了不與單詞發生交互,進行單獨構造,遇見單詞則判斷之後是否有單詞出現,如果沒有,從新的地址進行第一個單詞的判斷,如果有則調用詞組哈希構造,之後重復;

(1) 詞組哈希構造函數:

利用詞組的第一個單詞前4個字符與第二個單詞前4個字符的ASCLL值求和,平方取模,獲得地址,存入哈希表,構造一個結構體存儲詞組與頻率,在存儲的時候調用詞組比較與詞組優先比較函數,類似單詞哈希構造

(2) 詞組比較函數:

先判斷第一個單詞是否相同,再判斷第二個,具體操作類似單詞比較;

(3) 詞組優先判斷函數:

先判斷第一個單詞的優先,再判斷第二個,具體操作類似單詞優先比較;

6、 排序函數:

將單詞哈希表與詞組哈希表中的頻率進行排序,采取的是冒泡排序方法,為了同時輸出字符串和頻率,單獨構造了一個結構體存儲,此結構體存儲的是前十的字符串與頻率;

7、 文件輸出函數:

利用ofstream函數生成result.txt文件,並將答案寫入文件。

8、 main函數

主函數中調用了一次文件遍歷子函數,便是輸出的代碼。技術分享圖片

測試結果:

自己構造的測試集:

文件小的時候只能輸出正確的行數、字符數與單詞數,文件一大程序就會崩潰;

助教給的測試集:

無法全部跑完;

崩潰原因分析:

1、 在讀取字符的時候使用的是getline,但之後的調試與編譯的時候,出現了數組溢出的事情,所以在get與getline的選擇上出現了錯誤,應該選擇更加安全的get,

2、 對於哈希表的構造不夠好,由於自己是用結構體數組,也會出現數據溢出的問題,還有關於哈希函數的構造也不夠好,容易發生沖突,導致的大量的計算與調用函數,使得程序運行很慢;

3、 在判斷單詞與詞組的時候,需要考慮的東西很多,很容易就會忽視一個,導致程序無法正常運行,由於多條條件的判斷導致自己很容易出錯;

4、 比較難以實現的是對哈希表的排序,最終確定了使用冒泡排序,這也導致了運行時間過長;

5、 各類細節問題,例如一開始使用的是int型數據,到最後才意識到了需要使用長整型,導致了代碼很多地方需要修改,以至於發生了一些不可描述的問題;

代碼優化:

1、 在一些循環語句中,例如for循環,一開始判定的條件是到達數組地址最大值的時候停止,經過考慮,於是使用了計算字符串長度的函數,將條件表達式的裏的值改成了字符串真正懂得長度,但任然大量的調用了strlen函數,在代碼交上以後又想到了可以直接使用do……while循環,直接判斷數組為空,便可停止循環;

2、 盡量使用有符號長整型,但為了答案不會出錯,我選擇了無符號,這會大量浪費運行時間,應該將不需要無符號的數改為有符號;

3、 函數調用過於深,這次的程序發生了5層函數的調用,最好可以將其簡化;

4、 有一點沒用註意,便是沒用多使用const,因為如果一個const聲明的對象的地址不被獲取,允許編譯器不對它分配儲存空間。這樣可以使代碼更有效率,而且可以生成更好的代碼。

5、 熱行分析

技術分享圖片

由於采用了無比慢的排序方法導致的兩個sorting函數運行所占時間很多;

教訓與總結

首先從架構來說,自己在項目一開始的時候沒有確定一個特別明確的架構,導致在編碼的時候發現了思路不清楚的問題,之後才逐漸完善了架構,便浪費了不少時間,一個好的架構不僅在開發上有助於代碼的實現,還能解決程序耦合的問題;其次是沒有一個明確的設計方案,只是大致一想,就開始編碼,一邊編一邊想解決方案,導致了很深的函數調用,最終不易於調試與修改;再是自己在編碼的時候忘記了先編好輸出以便於一邊編一邊測試,導致了自己苦於編好了很多函數卻在測試時跳出了無數的bug,而且不易於修改,這個問題老師很早在課堂上提到過,要編幾行就進行測試,而自己卻沒有註意,運行的時候十分的後悔,過於復雜的程序導致了我不知如何去修改;最後就是書到用時方恨少,對於文件遍歷的問題自己將近花費了五分之一的時間,就是由於自己對於這個方面了解的太少,只能臨時抱佛腳,還有本來可以用C++的一些類就可以很容易解決的問題,只能自己利用C語言復雜的進行編寫,例如同學使用了一個C++中map的類就可以解決存儲的問題,自己感覺用的方法都很“蠢”,不夠靈活。

在這次個人作業中,我深深地感受到了自己在編碼方面的嚴重不足,不僅是對於各個語言的理解,還有是對於它們的靈活運用,由於自己知識儲備的嚴重不足,遇到問題都無法及時給出一個合適的解決方案,需要一個個去百度,甚至是去從頭去看書,在這個地方花費的時間將近有四分之一,導致了自己的思路不夠流暢,所以要加強自己對於編程書的閱讀,不能再拘泥於老師布置的任務,從現在開始要學習C++等更加便捷的語言,還有以後在編程之前要進行一次對於設計的思考,等到自己感覺設計已經明確並且可行時,再進行編碼,還有就是要註意好每一個細節,盡量在編碼的時候就最好註意到,最後也是我印象最深刻的就是隨時編碼隨時測試,多寫註釋,避免遺忘。綜上所述,簡言之就是“無他,唯手熟爾”,以後自己要多思考、多編程。

第一次工作報告