第三次作业 结对编程
结对编程完成WordCount
合作者:201731062301 201731062304(学号)
本次作业链接:https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/2882
一.Github项目地址及作业地址
1.结对使用的Github项目地址:https://github.com/qili12/WordCount.git
2.结对伙伴的作业地址:https://www.cnblogs.com/3451ymx/p/10647277.html
二.结对过程及填写PSP表格
1.结对过程
找好结对的小伙伴,根据代码功能模块的要求,进行模块编写的分工。
按照掌握知识的熟练程度以及对不同知识掌握运用能力来分工合作完成这个项目。
2.填写PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
45 |
45 |
· Estimate | · 估计这个任务需要多少时间 | 45 | 45 |
Development |
开发 |
910 |
1295 |
· Analysis | · 需求分析 (包括学习新技术) | 45 | 60 |
· Design Spec | · 生成设计文档 | 30 | 45 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 45 | 60 |
· Coding | · 具体编码 | 600 | 800 |
· Code Review | · 代码复审 | 60 | 90 |
· Test | · 测试(自我测试,修改代码,提交修改) | 80 | 150 |
Reporting |
报告 |
90 |
115 |
· Test Report | · 测试报告 | 30 | 45 |
· Size Measuremen | · 计算工作量 | 25 | 25 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 35 | 45 |
合计 |
1045 |
1455 |
总结分析:在本次项目完成后,发现预估时间和实际时间相差很大,主要表现在开发阶段,其中又主要集中在具体编码和测试、修改中。在具体编码过程中困难重重,因此花费了较多时间。
三.解题思路
项目目标:实现命令行程序。
1、统计字符数,行数,读入文件的时候有很多种读入方式,比如ReadLine,ReadToEnd等等,选择一行一行读入,同时记录行数,并且记录每一行的有效字符串统计长度作为字符数。
2、统计单词频率。首先需要判断是否为单词,采用正则表达式。把所有符合单词的要求的单词放入一个list当中,再用字典进行频率统计和排序。
3、输出到文件。还是采用字典排序,将单词输出到一个文件。
先把大概的框架梳理清楚,再去查找相应的资料。
在设置相应的命令行参数的时候,-i作为输入的文件路径,必须存在。在输入其他参数的时候要判断是否符合,不做无效输出。
四.设计实现过程
使用一个WordList类封装统计字符,单词频率、单词个数、按字典序输出到文件四个个方法,在WordCount中调用。
具体代码思路:
五.制定的代码规范
C#项目代码规范:https://www.cnblogs.com/3451ymx/p/10648426.html
六.改进
在这一块不是很懂,感觉在效能分析上好像有些问题,没有找到原因。
消耗最大的函数:
static void Main(string[] args) { int countLine = 0; string str = ""; string path = ""; int phraseNum = 0; int wordFreNum = 0; for (int i = 0; i < args.Length; i += 2) // 判断输入参数 { switch (args[i]) { /* -i 参数设定读入文件的路径*/ case "-i": path = args[i + 1]; break; /* -m 参数设定的词组长度*/ case "-m": phraseNum = int.Parse(args[i + 1]); break; /* -n 参数设定输出单词数量*/ case "-n": wordFreNum = int.Parse(args[i + 1]); break; /* -o 参数设定生成文件的存储路径*/ case "-o": break; } } //当文件路径存在时 if (File.Exists(path)) { StreamReader sr = new StreamReader(path, Encoding.Default); string line; while ((line = sr.ReadLine()) != null) { countLine++; str += line + "\n"; } sr.Close(); str = str.Trim(); //如果含有-o参数 将显示内容输出到文件中 for (int i = 0; i < args.Length; i++) { if (args[i] == "-o") { FileStream fs = new FileStream(args[i + 1], FileMode.Create); StreamWriter sw = new StreamWriter(fs); sw.WriteLine("Characters:" + WordsList.CountChar(str)); sw.WriteLine("Lines: " + countLine); sw.WriteLine("Words:" + WordsList.CountWords(str)); //如果有-n参数且有大于零的输入,调用PutNwords函数 if (wordFreNum>0) { sw.WriteLine("输出频率前"+wordFreNum+"的词组:"); Dictionary<string, int> item = PutNwords(str).OrderByDescending(r => r.Value).ThenBy(r => r.Key).ToDictionary(r => r.Key, r => r.Value); int size = 0; foreach (KeyValuePair<string, int> entry in item) { string word = entry.Key; int frequency = entry.Value; size++; if (size > wordFreNum) break; sw.WriteLine(word + ":" + frequency); } } //如果有-n参数且大于零的输入,则调用phraseNum函数 if(phraseNum > 0) { sw.WriteLine("输出长度为" + phraseNum + "的词组:"); Dictionary<string, int> item = PhraseFre(str,phraseNum).OrderByDescending(r => r.Value).ThenBy(r => r.Key).ToDictionary(r => r.Key, r => r.Value); foreach (KeyValuePair<string, int> entry in item) { string word = entry.Key; int frequency = entry.Value; sw.WriteLine(word + ":" + frequency); } } sw.Flush();//关闭流 sw.Close(); Console.WriteLine("文件已创建在:" + args[i + 1]); } } } else Console.WriteLine("没有文件路径或文件不存在!"); }
七.代码说明
WordList的类中方法:
判断单词是否符合要求(使用正则表达式来判断):
//判断单词是否符合要求,若符合用列表存储。 public static List<string> Judge(string path) { List<string> list = new List<string>(); string[] wordsArr1 = Regex.Split(path, "\\s*[^0-9a-zA-Z]+"); foreach (string word in wordsArr1) { if (Regex.IsMatch(word, "^[a-zA-Z]{4,}[a-zA-Z0-9]*"))//判断前4个是否为字母 { list.Add(word); } } return list; }
统计单词频率(用字典存储方式,可以很好地取用键值对):
//统计单词词频并输出前10 public static void CountWordFre(string path) { List<string> list = new List<string>(); list = Judge(path); Dictionary<string, int> frequencies = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); foreach (string word in list) { if (frequencies.ContainsKey(word)) { frequencies[word]++; } else frequencies[word] = 1; } Dictionary<string, int> item = frequencies.OrderByDescending(r => r.Value).ThenBy(r => r.Key).ToDictionary(r => r.Key, r => r.Value); int size = 0;//限定输出个数 foreach (KeyValuePair<string, int> entry in item) { string word = entry.Key; int frequency = entry.Value; size++; if (size > 10) break; Console.WriteLine(word + ":" + frequency); } }
统计词组频率(仅考虑词组的相对位置只要是前后关系即可)
private static Dictionary<string,int> PhraseFre(string path,int n) { List<string> list = new List<string>(); list = WordsList.Judge(path);//list是符合单词要求单词的集合 Dictionary<string, int> frequencies = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); string s = ""; for (int i = 0; i <= list.Count - n; i++)//将长度为n的词组当成一个字符串 { int j; for (s = list[i], j = 0; j < n - 1; j++) { s += " " + list[i + j + 1]; } if (frequencies.ContainsKey(s)) { frequencies[s]++; } else frequencies[s] = 1; } return frequencies; }
命令行程序执行结果:
八.单元测试
1、代码复审
在写完各自分工的模块后,进行代码复审。主要在编程规范和单元测试两个方面,对代码不易读懂的地方进行代码注释,修改命名方式。
对程序集中的公开类和公共属性进行单元测试。
2、问题
在单元测试方面主要针对的是对单词是否符合要求的判断(在我们的代码过程中是重点),通过测试发现输入的文件中的单词不能为空。
若为空值,则会触发异常,但实际上是允许为空,但不执行后续代码。
3、解决:使用了不正确的断言方式,输入空值的时候应该断言list中是否为空。
4、在单元测试的编写上,看了一些关于单元测试编写的例子,对单元测试有一定的了解,但是在编写的过程中还是不知要如何下手,单元测试的代码覆盖率应该要达到100%,显然本次的单元测试并不是合格的,还有很多需要改进和学习的方法,写出好的单元测试。
[TestClass] public class UnitTest1 { [TestMethod] public void TestCountChar() { string str = "Abcd aa!"; int count = str.Length; Assert.AreEqual(WordsList.CountChar(str), count); string str1 = ""; Assert.AreEqual(WordsList.CountChar(str1), str1.Length); } [TestMethod] public void TestJudgeWords() { string str = null; Assert.IsNull(WordsList.Judge(str)); } [TestMethod] public void TestCountWord() { string str = null; List<string> list = new List<string>(); list = WordsList.Judge(str); Assert.IsNull(list); } }
九、感想
在具体编码过程中,对程序修改了很多次,主要是在程序的模块化,使得相互之间可以更好地调用对方的代码。通过这次结对编程发现1+1>2,每个人对同一个问题都有不同的解题思路和想法,两个人在一起完成一项作业可以更好地交流对问题的看法,找到一些更好地解决办法。相比较于自己编程,思路出现错误可能会卡壳很久,两个人进行结对,可以相互学习,拓展编程思路,更快的完成。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步