結對編程
一、博客開頭
作業要求地址 | https://www.cnblogs.com/harry240/p/11524113.html |
---|---|
Github地址 | https://github.com/1517043456/WordCount.git |
結對伙伴博客 | https://www.cnblogs.com/xnch/ |
二、描述結對過程
三、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 20 | 30 |
.Estimate | .估計這個任務需要多少時間 | 400 | 600 |
Development | 開發 | 300 | 350 |
.Analysis | .需求分析(包括新技術學習) | 30 | 30 |
· Design Spec | · 生成設計文檔 | 20 | 30 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 10 | 30 |
· Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 20 | 20 |
· Design | · 具體設計 | 30 | 30 |
· Coding | · 具體編碼 | 300 | 500 |
· Code Review | · 代碼復審 | 30 | 60 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 30 |
Reporting | · 報告 | 30 | 60 |
· Test Report | · 測試報告 | 60 | 30 |
· Size Measurement | · 計算工作量 | 30 | 30 |
·Postmortem & Process Improvement Plan | · 事后總結, 並提出過程改進計划 | 30 | 30 |
合計 | 1370 | 1640 |
四、解題思路描述
本次項目的主要難點有三:
(1)、命令行程序傳入指令的獲取和將指令分解:
解決思路和方法:以命令行的形式運行程序,實際上在WordCount.exe的后面的指令就是主函數main中傳入的參數,而這個參數是字符串數組,因此將可以將其直接使用,而單個類似於input.txt 這種指令的參數,可以直接從中獲取,因為指令的形式是:【指令 參數】這樣的形式。在傳入的主函數中的字符串數組是按照順序存儲的,直接根據下表索引即可。(2)、將txt文件中的信息進行相應的處理,獲得符合要求的單詞形式:
解決思路和方法:毋庸置疑,對從txt文件中獲取的數據流來分割為符合要求的單詞形式應該使用正則表達式:
//用於分割篩選出每行的字母數字
string argex1 = "\s[^0-9a-zA-Z]+";
//用於分割單詞
string argex2 = "[a-zA-Z]{4,}[a-zA-Z0-9]";(3)、對於單詞的計數和排序:
解決思路和方法:計數比較簡單直接從List取得數據的個數就是單詞數量。而統計單詞對應的頻率數則需要使用字典,鍵為單詞,值為個數。
五、設計實現過程
1、需求分析
本次項目需要實現的主要功能:
- 命令行和指令的分解
- 文件的讀取
- 字符統計
- 行數統計
- 單詞數量統計、當個單詞的統計
- 單詞詞頻的統計和排序(輸出頻率最高的10個。頻率相同的單詞,優先輸出字典序靠前的單詞)
2、程序流程圖

3、項目的類及其解決方案
4、類結構和單元測試的設計
本次的單元測試要求盡可能多的覆蓋到項目的每一個分支,因此每個類的方法應該足夠的分離,也就是方法足夠多,職責分明,同時滿足耦合度低的要求。因此本次的類和方法的具體如下:
Program類:
主類, 只含有main函數。
SplitKeys類:
public void ActKeys(string[] keys)** 方法,分解主類傳來的指令並調用指定的方法。
FileUtil類:
(1) getWordList方法,用於讀入文件並在讀的時候開始統計字符數和行數。
(2)public void printCountToFile(string result,string output)方法,用於將結果寫入文件
Count類:
(1)public string CountWord(int n,FileUtil fileUtil),用於執行 -n 指令。統計單詞數量以及單詞頻率,並排序。
(2)public string sumWord(int m,FileUtil fileUtil),用於執行-m指令,統計詞組數量並轉發結果。
5、代碼規范和鏈接
本次代碼的規范主要采用比較大眾的一個規范,我們就在網上找了一份我們適合我們經常編寫代碼習慣且比較具體的代碼規范。
代碼規范鏈接
六、代碼說明
- 1、指令解析
SplitKeys類
,用於將從控制台獲取到的指令進行解析,並根據不同指令調用不同功能,相當於整個項目的調度中心。
(1)指令獲得和調用
//混合指令的使用
for (; i < keys.Length; i++) {
if (keys[i].Equals("-i")&&keys[i+1]!=null)
{
fileUtil.Filepath = keys[i+1];
}
if (keys[i].Equals("-m"))
{
m = int.Parse(keys[i + 1]);
}
if (keys[i].Equals("-n"))
{
n = int.Parse(keys[i + 1]);
}
if (keys[i].Equals("-o"))
{
o = keys[i + 1];
}
}
當然這里也可以使用Directionary
來以鍵值對的形式進行存儲,即Directionary<stirng,string>
中,存入<-i,input.txt>
指令,這樣應該是十分標准的控制台指令存儲形式,不過可能會加大代碼的復雜性。
(2)、實例化兩個類,以便於調用功能。
FileUtil fileUtil = new FileUtil();
Count count = new Count();
- 2、文件的讀入和寫出
文件的讀入和寫出由FileUtil類
進行,將txt文件讀入,並在讀入的過程中進行簡單的字符數和行數的統計以及單詞的分割,最終返回一個處理過后的單詞鏈表,以及鍵最終結果進行寫入txt文件。
(1)、文件的讀入和字符數和行數的統計
try
{
StreamReader sr = new StreamReader(this.Filepath, Encoding.Default);
//用於存儲讀取后的單詞
List<string> wordList = new List<string>();
//用於分割篩選出每行的字母數字
string argex1 = "\\s*[^0-9a-zA-Z]+";
//用於分割單詞
string argex2 = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
//讀取一行
string readLine;
while ((readLine = sr.ReadLine()) != null) {
CountChar += readLine.Length;
CountLine++;
string[] wordArray = Regex.Split(readLine, argex1);
foreach (string word in wordArray) {
if(Regex.IsMatch(word,argex2))
wordList.Add(word);
}
}
sr.Close();
return wordList;
}
在讀入的時候進行字符數的統計和行數的統計,提高了效率。
這里用到的兩個正則表達式:
//用於分割篩選出每行的字母數字
string argex1 = "\\s*[^0-9a-zA-Z]+";
//用於分割單詞
string argex2 = "[a-zA-Z]{4,}[a-zA-Z0-9]*";
(2)、將結果寫入文件
try
{
string txtfile = output;
StreamWriter sw = new StreamWriter(txtfile);
sw.Write(result);
sw.Flush();
sw.Close();
Console.WriteLine(output+"文件寫入完畢!");
}
catch (Exception e)
{
Console.WriteLine("文件寫入失敗!");
}
- 3、單詞的相關統計和排序
(1)、單詞和對應詞頻統計
public string CountWord(int n,FileUtil fileUtil) {
String resultWord = null;
//需要輸出單詞數
int instructionN = n;
//獲取已經分割了的單詞列表
List<string> wordList = fileUtil.getWordList();
//用於將單詞進行排序和存儲
Dictionary<string, int> keyValuePairs = new Dictionary<string, int>();
foreach (string word in wordList) {
if (keyValuePairs.ContainsKey(word))
{
keyValuePairs[word]++;
WordCount++;
}
else {
keyValuePairs[word] = 1;
WordCount++;
}
}
(2)、排序
var queryResults = from ni in result//字典序排序
orderby ni
select ni;
foreach (var item in queryResults)
{
resultWord += item.ToString()+"\n";
}
這里使用的是.NET3.0
提供的字典排序。
4、結果截圖:
(1)、控制台結果截圖:
(2)、output.txt文件輸出截圖。
七、單元測試結果
(1)初步測試
(2)、更據結果測試結果后更正后:
八、性能改進
本次性能分析,主要是從算法入手和設計入手。首先在初步完成項目后,我們就進行了一次項目性能分析,來查看哪里的優化效率和算法。
可見,當我們為了完成功能,將所有代碼寫到一起時,有的代碼重復率十分高,消耗也大,耦合度也大。因此我們進行了相應的優化和改進。
當我們將代碼進行整理后,代碼的重復率下降,耦合性降低,同時也通過注入對象的方式減少了類實例化的開銷。
八、總結
本次結對編程,和自己的老搭檔兼室友許自歡一起進行的。整個過程還算和和諧。他經常給我指出問題,當遇到問題時總是及時的給出一個解決方案。兩個人去完成一個項目,無論是前期查閱相關資料還是后期的編碼,都更加的快速和有效。當出現問題時,兩個人一起解決,不同的思考方式總是想到很多方法,很快的將問題解決掉。結對編程剛開始時,的確有點不適應,一個人在編程的時候另外一個人有點迷惑不知道干嘛,因為不太習慣對方的編程方式和寫法,但是到了后面熟悉后速度和效率就提高了。
九、關於性能分析的改進
根據鄒老師的提問,我們更改了測試的方式拼接了10多本英文小說,獲得了一個29411k的txt文件來測試,在這個測試文件中的字符數達到了:29437257個。
同時我們發現了消耗最大的方法是ActKeys()
和getWordList()
,前一個方法消耗很大應該是因為它作為一個指令調用的主要分發點,寫了很多循環和判斷,導致消耗很大。而后者這是讀取文件並且判斷統計字符數、函數和將分割的單詞加入到鏈表中的方法,因此它開銷很大,而后面幾個的開銷顯著減小,是因為我們在getWordList()
方法執行后返回給它們的是處理好的List
,而這種數據結構減少了其他方法對於單詞的處理消耗,同時我們用這種以一個很大的文件來測試程序性能的方式也體現了我們對性能改進的解決方案即將類實例化一次然后傳給其他類使用的優點。