做業要求地址 | http://www.javashuo.com/article/p-zywsxnlu-cu.html |
GitHub地址 | https://github.com/anranbixin/WordCount |
結對夥伴的博客 | http://www.javashuo.com/article/p-kgwqkmre-e.html |
PSP表格css
PSP2.1html |
Personal Software Process Stagesnginx |
預估耗時(分鐘)git |
實際耗時(分鐘)程序員 |
Planninggithub |
計劃正則表達式 |
30編程 |
40數組 |
· Estimate函數 |
· 估計這個任務須要多少時間 |
1200 |
1440 |
Development |
開發 |
1080 |
1200 |
· Analysis |
· 需求分析 (包括學習新技術) |
40 |
50 |
· Design Spec |
· 生成設計文檔 |
20 |
20 |
· Design Review |
· 設計複審 (和同事審覈設計文檔) |
20 |
30 |
· Coding Standard |
· 代碼規範 (爲目前的開發制定合適的規範) |
30 |
30 |
· Design |
· 具體設計 |
60 |
50 |
· Coding |
· 具體編碼 |
700 |
960 |
· Code Review |
· 代碼複審 |
60 |
50 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
120 |
150 |
Reporting |
報告 |
30 |
20 |
· Test Report |
· 測試報告 |
60 |
40 |
· Size Measurement |
· 計算工做量 |
30 |
20 |
· Postmortem & Process Improvement Plan |
· 過後總結, 並提出過程改進計劃 |
30 |
20 |
|
合計 |
1230 |
1480 |
代碼要求:
新增要求:
a) 詞組統計:能統計文件夾中指定長度的詞組的詞頻
b) 自定義輸出:能輸出用戶指定的前n多的單詞與其數量
(1).-i 參數設定讀入的文件路徑 wordCount.exe -i input.txt
(2).-m 參數設定統計的詞組長度 wordCount.exe -m 3 -i input.txt
(3)-n參數設定輸出的單詞數量
(4)-o參數設定生成文件的存儲路徑,則將統計信息輸出到文件output.txt中
(5)多參數的混合使用
解題思路過程:
剛開始拿到題目時,咱們一片茫然,不知所措,由於以前落下的C#的學習致使後面編寫代碼時出現了不少問題,
好比:
1.怎樣將命令行中的路徑經過命令行傳入到函數中,使得函數經過命令行的文本路徑,獲得文本input.txt中的單詞數、行數、字符數。在這裏感謝前面的師兄師姐和博客園其餘給咱們提供幫助的人們。
當時並不打算作附加問題,也不知道怎樣把命令行中的路徑給提取出來,若作-i,-m,-n,-o問題就能直接從args[i]中讀取-i後面的一個字符串,經過-i命令提示符獲得字符路徑,同理經過-o命令提示符獲得文本存儲路徑,經過-n [number]獲得number,經過-m [number]獲得number。
(PS:咱們如今才知道static void Main(string[] args)中的string[] args 指的是咱們在命令窗口輸入的參數,經過命令窗口接收命令。如今才知道它的做用,後悔當初沒認真聽課!)
2.怎樣提取路徑path中input.txt文本中的字符串。當時咱們設想將整個文本使用StreamReader sw = file.OpenText();string temp = sw.Read();經過流的方式一個字符一個字符的進入流中,但若是這樣實施的話,就會致使後面的行數沒法肯定,並且讀取速度也很是慢;
若使用按行讀取的方法StreamReader content = new StreamReader(path);string temp = content.ReadLine();這樣讀進流中時,就能直接肯定input.txt中的文本行數 ,讀取速度也較快。
3.怎樣判斷這個單詞知足上述條件
並計算其頻數。首先咱們要明確題目說的意思,四個英文字符開頭,後面結尾能夠用數字英文字符,單詞與單詞之間使用分隔符分割,但如何判斷該單詞知足其條件呢?這些都是咱們要解決的問題。
當時咱們走了不少彎路,決定用ASII碼值經過 97<=words[i]<=122||65<=words[i]<=90 這樣的方式來判斷單詞前四個字符都是英文,但咱們以爲這樣太麻煩了,因而在網上查找資料,發現可使用正則表達式直接判斷,利用MatchCollection mc_word = Regex.Matches(account_chara, @"([a-zA-Z]{4}\w*)")方法,其中[a-zA-Z]是用來判斷英文字符,{4},判斷前面4個字符是否都知足[a-zA-Z]的條件,這樣一來直接經過正則表達式就能夠判斷單詞是否知足上述條件。而單詞個數能夠經過函數WordNum()的word
.Count直接獲得。
經過正則表達式判斷好知足條件的單詞咱們將它加入到一個result列表中,那怎樣存儲頻數呢?首先咱們須要將全部大小寫統一轉換成小寫,方便系統檢測,此時咱們可使用字典將知足條件的單詞做爲key,將其頻數做爲value。
此時知道如何存儲單詞和頻數了,那如何計算頻數呢?此時須要字典Dictionary的ContainsKey方法,若單詞字典中第一次出現該單詞,則將該單詞的value的值賦爲1,若此前出現過該單詞,則將該單詞中的value值就+1。
4.-i和-o命令提示符後的路徑均可以經過Directory.GetCurrentDirectory()方法直接獲取當前路徑filepath,加上咱們後面的文件input.txt,即path=path+"\\"+input.txt,但這裏出現了一個問題,當時這樣運行時,發現了一個問題,系統提示「沒法獲取當前文件路徑」,咱們檢查了 好久,一直沒發現到底哪兒錯了,後面經過在控制檯中打印整個路徑,發現其中path和"input.txt"中間沒有用"/"鏈接,後面咱們將路徑更改成path=path+"\\"+input.txt才得以解決這個問題。
5.-n後面加上數字則表示設定輸出的單詞數量,經過開始將單詞做爲key,將頻數做爲value時,使用Dictionary的OrderByDescending方法經過value的值--單詞頻數倒序排序,這樣咱們就能夠直接找到前n個高頻數的單詞並將其打印出來。
6.-m後面加上數字是表示以這個數字表示詞組的單詞個數,所以物品們能夠利用循環嵌套的方法將詞組放在一個集合中,最後在控制檯中打印出來,並將其結果放入output.txt文件中。
不知道怎樣把命令行中的路徑給提取出來,若作-i,-m,-n,-o問題就能直接從args[i]中讀取-i後面的一個字符串,經過-i命令提示符獲得字符路徑,同理經過-o命令提示符獲得文本存儲路徑,經過-n [number]獲得number,經過-m [number]獲得number。
(PS:咱們如今才知道static void Main(string[] args)中的string[] args 指的是咱們在命令窗口輸入的參數,經過命令窗口接收命令。如今才知道它的做用,後悔當初沒認真聽課!)
2.怎樣提取路徑path中input.txt文本中的字符串。當時咱們設想將整個文本使用StreamReader sw = file.OpenText();string temp = sw.Read();經過流的方式一個字符一個字符的進入流中,但若是這樣實施的話,就會致使後面的行數沒法肯定,並且讀取速度也很是慢;
若使用按行讀取的方法StreamReader content = new StreamReader(path);string temp = content.ReadLine();這樣讀進流中時,就能直接肯定input.txt中的文本行數 ,讀取速度也較快。
功能模塊圖以下:
1. GetCharacters (string path)函數的流程圖
2.public void WordFrequency()流程圖以下:
咱們本着「保持簡明,讓代碼更容易讀」的原則,讓咱們更好地理解和維護程序。
代碼風格的原則是:簡明,易讀,無二義性。
1.縮進:
4個空格,在VS2017和其餘的一些編輯工具中均可以定義Tab鍵擴展成爲幾個空格鍵。不用 Tab鍵的理由是Tab鍵在不一樣的狀況下會顯示不一樣的長度。4個空格的距離從可讀性來講正好。
2.括號:
在複雜的條件表達式中,用括號清楚地表示邏輯優先級。
3.斷行與空白的{ }行:
每一個「{」和「}」都獨佔一行
如:
if ( condition) { DoSomething(); } else { DoSomethingElse(); }
4.
分行:
不要把多行語句放在一行上。
5.命名:
命名方法使用「匈牙利命名法」,在變量面前加上有意義的前綴,就可讓程序員一眼看出變量的類型及相應的語義
例如:
fFileExist,代表是一個bool值,表示文件是否存在;
szPath,代表是一個以0結束的字符串,表示一個路徑。
6.
下劃線問題:
下劃線用來分隔變量名字中的做用域標註和變量的語義
7.大小寫問題:
由多個單詞組成的變量名,若是所有都是小寫,很不易讀,一個簡單的解決方案就是用大小寫區分它們。
Pascal——全部單詞的第一個字母都大寫;
Camel——第一個單詞所有小寫,隨後單詞隨Pascal格式,這種方式也叫lowerCamel。
一個通用的作法是:全部的類型/類/函數名都用Pascal形式,全部的變量都用Camel形式。
類/類型/變量:名詞或組合名詞,如Member、ProductInfo等。
函數則用動詞或動賓組合詞來表示,如get/set; RenderPage()。
8.註釋:
複雜的註釋應該放在函數頭,養成邊寫代碼邊寫註釋的好習慣
代碼說明以下:
將控制檯上的命令經過參數args傳入,獲得-i -o -n -m選項的混合使用的命令,並將其中-n [number]和-m [number]中的number分別傳入變量n和m中,代碼以下:
for (int i = 0; i < args.Length; i++)//將控制檯上的命令經過參數args傳入 { switch (args[i]) { case "-i": path = args[i + 1];//輸入路徑 break; case "-o"://-o輸出路徑 outpath = args[i + 1]; break; case "-m"://-m輸出幾個高頻詞 m = args[i + 1]; break; case "-n"://-n輸出幾個單詞的個數 n = args[i + 1]; break; default: Console.WriteLine("輸入有誤,請檢查"); break; } }
GetCharacters將文件中的文本以字符串的形式存儲進account_chara,同時計算文本行數account_line
public void GetCharacters(string path) { StreamReader content = new StreamReader(path);//爲StreamReader建立實例 string temp = content.ReadLine();//定義字符臨時變量,按行讀入 while (temp != null)//當讀取的這一行不爲空時,執行如下功能 { account_chara = account_chara + temp;//將讀入的文本經過字符串的形式存儲在account_chara account_line++;//計算行數 Console.WriteLine(account_line); temp = content.ReadLine();//讀取下一行 } }
WithdraWord()是利用正則表達式從字符串account_chara中匹配英文單詞(該英文單詞要求爲:以字母開頭,數字結尾,單詞至少4個字符)。
其中([a-zA-Z]{4}\w*)中[a-zA-Z]表示英文字符,{4}表示前面[a-zA-Z]重複4次。
將知足條件的單詞存放在word列表中,爲下面將單詞統一轉換成小寫和計算單詞頻數作準備。
//以字母開頭,數字結尾,單詞至少4個字符
public void WithdraWord() {
//MatchCollection表示以迭代的方式將正則表達式模式應用於輸入字符串所找到的成功匹配的集合 MatchCollection mc_word = Regex.Matches(account_chara, @"([a-zA-Z]{4}\w*)"); int i = 0;//臨時變量 while (i < mc_word.Count) { word.Add(Convert.ToString(mc_word[i]));//存儲單詞 i++; } }
計算知足條件的單詞個數
//單詞總數 public int WordNum() { return word.Count; }
將列表word中的全部字符元素所有轉換成小寫的形式,爲下面計算單詞頻數作準備。
public void ToLower()//將word中的單詞元素轉換成小寫 { foreach (string element in word) { element.ToLower(); } }
計算字符的個數,其中字符個數不包括中文的數量,所以須要排除中文的字符(正則表達式@"[^\u4e00-\u9fa5]*")
//字符總數 public int CharacterNum() { //區分是否爲中文,中文用正則表達式表示爲\u4E00-\u9FA5 MatchCollection mc_chara = Regex.Matches(account_chara, @"[^\u4e00-\u9fa5]*"); string temp = null; int i = 0; while (i < mc_chara.Count) { temp = temp + Convert.ToString(mc_chara[i]); i++; } return temp.Length; }
計算單詞頻數,將單詞與單詞頻數存放在d_word字典中,按降序排序,爲輸出前n個頻數作準備
public void WordFrequency() { Dictionary<string, int> d_word = new Dictionary<string, int>();//建立一個字典實例,將每一個不一樣單詞做爲key值,將其頻數做爲其value int i = 0; while (i < word.Count) { if (d_word.ContainsKey(word[i]))//判斷word[i]做爲d_word的key值是否存在 { d_word[word[i]]++;//若存在,則將word[i]對應的value值加1 } else { d_word[word[i]] = 1;//若不存在,則將word[i]做爲key值,將其所對應的value值令爲1 } i++; } //將d_word按照降序的方式排序(這樣能夠經過循環提取前n個最高頻率的單詞) word_num = d_word.OrderByDescending(p => p.Value).ToDictionary(p => p.Key, o => o.Value); }
WriteToFile(string path, string)將獲得的數據輸出到文件中
//寫入文件 public void WriteToFile(string path, string outpath) { Prep(path);//獲取文本的函數GetCharacters(path);提取符合題目要求的單詞函數WithdraWord();將全部字符轉換成小寫的函數ToLower();計算每一個單詞出現頻率函數WordFrequency() FileInfo file = null; if (outpath == null)//判斷outpath是否存在 { file = new FileInfo(@"D:\VS_practice\201731062402\output.txt");//若不存在則添加一個新的文件 } else//若存在則直接將該outpath賦給file { file = new FileInfo(outpath); } //將結果所有輸入到outpath路徑下的output.txt StreamWriter sw = file.AppendText(); sw.WriteLine("字符數:" + CharacterNum()); sw.WriteLine("單詞數:" + WordNum()); sw.WriteLine("行數:" + WordLineNum()); Console.WriteLine("字符數爲:" + CharacterNum()); Console.WriteLine("單詞數爲:" + WordNum()); Console.WriteLine("行數爲:" + WordLineNum()); //統計前10個高頻單詞 WriteWord(sw, 10); //關閉文件 sw.Close(); Console.WriteLine("結果文件保存於: \n D:\\VS_practice\\201731062402\\output.txt"); }
WriteWord(StreamWriter sw, int n)爲篩選出前n個高頻單詞並將其打印出來
public void WriteWord(StreamWriter sw, int n) { int flag = 0; foreach (KeyValuePair<string, int> element in word_num) { string key = element.Key; int value = element.Value; //寫入前n個高頻單詞,以及其頻數 if (flag < n) { Console.WriteLine("單詞 : {0}\t 頻數是 : {1}", key, value); sw.WriteLine("單詞 :{0}\t 頻數是 : {1}", key, value); flag++; } } }
WordGroup(StreamWriter sw, int m)計算以m個爲一組的單詞組,將其打印在屏幕上和output.txt中
public void WordGroup(StreamWriter sw, int m) { //Console.WriteLine("------------------------------"); //sw.WriteLine("------------------------------"); Dictionary<string, int> dc = new Dictionary<string, int>(); string list = null; int i, j; for (i = 0; i <= word.Count - m; i++) { list = word[i]; for (j = 1; j < m; j++) { list += " " + word[i + j]; } if (dc.ContainsKey(list)) { dc[list]++; } else { dc[list] = 1; } } Dictionary<string, int> tt = dc.OrderByDescending(p => p.Value).ThenBy(o => o.Key).ToDictionary(p => p.Key, o => o.Value); foreach (KeyValuePair<string, int> element in tt) { Console.WriteLine("詞組爲:{0}\t次數爲:{1}", element.Key, element.Value); sw.WriteLine("詞組爲:{0}\t次數爲:{1}", element.Key, element.Value); } }
成功運行後截圖以下:
附加功能-i -o -n -m 的截圖以下所示:
上述測試代碼爲:
[TestMethod()] public void GetCharactersTest() { StreamReader content = new StreamReader(@"G:\系統分析與設計(博客)\YP\wordCount\wordCount\bin\Debug\input.txt");//爲StreamReader建立實例 string temp = content.ReadLine();//定義字符臨時變量,按行讀入 while (temp != null)//當讀取的這一行不爲空時,執行如下功能 { account_chara = account_chara + temp;//將讀入的文本經過字符串的形式存儲在account_chara account_line++;//計算行數 Console.WriteLine("-----"); Console.WriteLine(account_line); temp = content.ReadLine();//讀取下一行 } Assert.IsNotNull(account_chara); }
下面對WithdraWordTest()作測試,代碼以下:
[TestMethod()] public void WithdraWordTest() { MatchCollection mc_word = Regex.Matches("ABcd123", @"([a-zA-Z]{4}\w*)");//MatchCollection表示以迭代的方式將正則表達式模式應用於輸入字符串所找到的成功匹配的集合 int i = 0;//臨時變量 while (i < mc_word.Count) { word.Add(Convert.ToString(mc_word[i]));//存儲單詞 i++; } Assert.IsNotNull(word); }
下列12個函數所有測試完畢併成功經過,截圖以下:(因爲測試代碼衆多,這裏只展現其中兩個,代碼如上所示)
根據性能分析,能夠看出Main函數中是CPU佔比最多的函數,因而咱們看到main函數,其中在該函數中,咱們讀取文件、將最後結果放在文件中所花時間、判斷命令行中出現的命令選項、判斷並打印結果等所花時間都不少,特別是文件的讀取過程很慢,所以咱們針對文件讀取作了不少分析,最後減小了程序對文件的讀取的個數使得性能加快。
而在另外一個類port中,WordFrequency()函數佔用率最高,所以咱們針對WordFrequency()函數進行了分析,WordFrequency()函數中主要拖垮性能的緣由是有大量的判斷語句,判斷d_word字典中的key值,即單詞word[i]是否存在,若存在則執行value值加1的操做,若不存在,則將其做爲key存入字典並將其value值令爲1,所以咱們在函數外將咱們須要判斷的值優先判斷,最後再根據其判斷值進行value的值操做,加快了程序性能。
結對編程編程過程圖片:
這一路磕磕絆絆,又從新學習了C#,對C#又有了更加深刻的瞭解,特別是泛型Dictionary的運用,它的結構是這樣的:Dictionary<[key], [value]>,Dictionary存入對象是須要與[key]值一一對應的存入該泛型,經過某一個必定的[key]去找到對應的值,由於這個題目須要大量元素以及該元素出現的頻率,最開始時,咱們學習的C#只停留在簡單的基礎操做中,沒有深刻的學習C#其中封裝好的函數,所以沒有想到用Dictionary的方法去作,一古腦兒的使用二維數組去作,中途碰到了不少bug難以解決,最後在網上查找資料,看到Dictionary可以很好的解決這個問題,所以咱們又從新開始使用庫提供給咱們的Dictionary,這個過程很是艱辛。雖然挫折一波接着一波,但咱們仍是共同努力,體會到1+1>2的好處,個人結對小夥伴在編寫這個函數的功能時,我就在看下一個函數如何設計,就這樣輪換着來,終於把這個項目給作出來了。
1+1有時候真的大於2!!