第三次作业结对编程

一、地址

  GitHub地址:https://github.com/haveadate/WordCount.git

  本人学号:201731062329

  结对编程伙伴作业地址:201731062205

  作业地址:第三次作业结对编程


 

二、结对过程

  我和我的搭档youthlll。选定了两方都有空的时间出来讨论,现制定了PSP表,然后根据各自水平,分配任务。各自的任务完成过后,先自审,再交由对方复审,然后汇总,封装成dll文件,进行单元测试和效能分析,并且改进代码,最后一起撰写博客。这是我们结对编程过程中的某一照片


 

三、PSP表格

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

 880

 1395

· Estimate

· 估计这个任务需要多少时间

 880

 1395

Development

开发

 760

 1270

· Analysis

· 需求分析 (包括学习新技术)

 40

 50

· Design Spec

· 生成设计文档

 30

 35

· Design Review

· 设计复审 (和同事审核设计文档)

 50

 60

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

 20

 15

· Design

· 具体设计

 30

 50

· Coding

· 具体编码

 400

 900

· Code Review

· 代码复审

 60

 40

· Test

· 测试(自我测试,修改代码,提交修改)

 100

 120

Reporting

报告

 120

 125

· Test Report

· 测试报告

 60

 60

· Size Measurement

· 计算工作量

 30

 40

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

 30

 25

 

合计

 880

 1395


 

四、解题思路

  刚开始看到题目有点懵,感觉无从下手。于是就和自己结对编程的伙伴讨论了一下,确实是自己被题给“吓”到了,静下心来思考了一下,其实就是读写文件,将每一行读取的文件用字符串的Split()方法将其分割成字符串数组,对字符串数组中的每个元素进行讨论即可。例如统计字符总数,其实就是读取文件每一行数据,直接用str.Length,即可获取其一部分长度,无论是制表符还是空格,都能够准确得到其长度,唯一要注意的就是,回车符,这个也可以通过其文件的行数通过逻辑判断得出,最后汇总即可得到文件中字符的总数。详细的请看下面代码说明部分。在结对过程中找资料主要是上网查资料,以及通过自己以前的《面向对象程序设计》获取一些文件操作信息。


 

五、设计实现过程

  此次作业设计,由于编程经验不足,没有将功能设计规划的很好。对于此次作业,只有一个FileOperate类,在该类中将作业要求的几个点都封装在一个方法中,即一个方法对应一个功能。各个方法之间的关系除了判断一个字符串是否为一个单词的IsWords()方法会被其它方法调用,方法之间都是相互独立的。获取文件中字符总数的charNumber()方法、统计文件中单词总数的wordNumber()方法、得到文件行数的lineNumber()方法思路并不是很复杂,在此展示判断一字符串是否是一个单词的isWords(string word)方法、获取文件中的频率最高的n个词的wordTimes(int n)方法、获取文件中指定词组的wordGroup(int len)方法的流程图:

  由于双重循环在流程图中太复杂,能力有限将在后面代码说明处将对wordTimes(int n)方法、wordGroup(int len)方法进行详细的说明,实在抱歉。

  针对于单元设计,由于设计的方法都有返回值,所以在生成的单元测试环境中,都是创建一个FileOperate的对象,然后通过调用相应的方法,然后使用断言判断进行单元测试。


 

六、代码规范等

  结对讨论出的代码规范如下:

    1.命名规则:使用驼峰命名法。给类或函数或字段命名,使用具有相应中文意思的英文单词;

      2.分行:不把多条语句放在一行上,不把多个变量定义在一行上;

      3.断行与空白的{}行:”{“”}”单独在一行;

    4.注释:在类或方法的上面使用文档注释,在方法中使用普通注释;

  针对于FileOperate类中的charNumber()方法、lineNumber()方法、wordNumber()方法、isWords()方法、wordTimes()方法、wordGroup方法,搭档之间将本人负责后面四个部分,在我们完成相应的方法之后,自己先测试,通过以后,再相互审查,求同存异。在这其中发现了很多问题,令我印象深刻的一个就是,搭档提醒我,英语的语法特性,“,”、“.”、“!”前面如果有单词,这个字符串也应该算作一个单词,这个问题直到现在在脑海里还非常的清晰。


 

七、改进程序  

  刚开始的main函数比较臃肿,通过vs的性能分析工具发现,main函数消耗最大。通过观察发现,由于一些必要对命令行参数的操作,里面循环很多。在改进过程中时间大概花了10分钟,原来在刚开始的对"-o"、"-i"、"-help"命令的遍历时,如果找到了执行相应操作就可以终止了,但是实际上循环会有不必要的消耗,于是我就对循环添加了break关键字,以减少了不必要的消耗,使消耗略微的减少了。第二大消耗是我使用了linq语句用于字典排序,但这个,我并不是很熟悉其内部的消耗,所以就没有改进。

  消耗最多的函数

  1 static void Main(String[] args)
  2 {
  3     //StreamWriter writeFile = new StreamWriter("out.txt", false);//false表示将文件覆盖,而不是追加
  4     //FileOperate fileOperate = new FileOperate("input.txt", writeFile);
  5     //初始化
  6     string[] arg = new string[] { "-i", "input.txt","-o","out.txt","-n","3","-m","2" };//测试代码
  7     StreamWriter writeFile = null;
  8     FileOperate fileOperate = null;
  9     //创建读文件对象
 10     for (int i = 0; i < arg.Length; i++)
 11     {
 12         if(arg[i] == "-help" || arg[i] == "help")//可以直接使用help命令
 13         {
 14             Console.WriteLine("https://www.cnblogs.com/haveadate/ [version 3.0]");
 15             Console.WriteLine("(c) 2019 haveadate 保留所有权利。\n");
 16             Console.WriteLine("-i  参数设定读入的文件路径       (必要)     格式:-i [file]");
 17             Console.WriteLine("-o  参数设定生成文件的存储路径   (必要)     格式:-o [file]");
 18             Console.WriteLine("-m  参数设定统计的词组长度       (非必要)   格式:-m [number]");
 19             Console.WriteLine("-n  参数设定输出的单词数量       (非必要)   格式:-n [number]");
 20             Console.WriteLine("注: 参数顺序不对结果产生影响");
 21             return;
 22         }
 23         if(arg[i] == "-i")
 24         {
 25             fileOperate = new FileOperate(arg[i + 1]);
 26             break;//减少不必要的运行
 27         }
 28     }
 29     //创建写文件对象
 30     for (int i = 0; i < arg.Length; i++)
 31     {
 32         if (arg[i] == "-o")
 33         {
 34             writeFile = new StreamWriter(arg[i + 1], false);
 35             //三个必须输出
 36             writeFile.WriteLine("characters: " + fileOperate.charNumber());
 37             writeFile.WriteLine("words: " + fileOperate.wordNumber());
 38             writeFile.WriteLine("lines: " + fileOperate.lineNumber());
 39             break;
 40         }
 41     }
 42     //附加命令处理
 43     for (int i = 0; i < arg.Length; i++)
 44     {
 45         if(arg[i].StartsWith("-"))//表明该项是输入参数
 46         {
 47             switch(arg[i])
 48             {                  
 49                 case "-m":
 50                     int len = int.Parse(arg[i + 1]);//将字符串转换成数字                            
 51                     Dictionary<string, int> temp1 = fileOperate.wordGroup(len);
 52                     foreach (KeyValuePair<string, int> keyValuePairs in temp1)//Dictionary的foreach遍历对象自动转换成KeyValuePairs
 53                     {
 54                         writeFile.WriteLine(keyValuePairs.Key + ": " + keyValuePairs.Value);
 55                         writeFile.Flush();//将缓冲区的文件写到基础流                                
 56                     }
 57                     break;
 58                 case "-n":
 59                     int n = int.Parse(arg[i + 1]);//将字符串转换成数字
 60                     //Console.WriteLine(n);
 61                     Dictionary<string, int> temp2 = fileOperate.wordTimes(n);
 62                     foreach (KeyValuePair<string, int> keyValuePairs in temp2)//原理转换不是很清除,命名是推荐的
 63                     {                               
 64                         writeFile.WriteLine(keyValuePairs.Key + ": " + keyValuePairs.Value);
 65                         writeFile.Flush();//将缓冲区的文件写到基础流                                
 66                     }
 67                     break;
 68                 case "-help":
 69                     Console.WriteLine("https://www.cnblogs.com/haveadate/ [version 3.0]");
 70                     Console.WriteLine("(c) 2019 haveadate 保留所有权利。\n");
 71                     Console.WriteLine("-i  参数设定读入的文件路径       (必要)     格式:-i [file]");
 72                     Console.WriteLine("-o  参数设定生成文件的存储路径   (必要)     格式:-o [file]");
 73                     Console.WriteLine("-m  参数设定统计的词组长度       (非必要)   格式:-m [number]");
 74                     Console.WriteLine("-n  参数设定输出的单词数量       (非必要)   格式:-n [number]");
 75                     Console.WriteLine("注: 参数顺序不对结果产生影响");
 76                     break;
 77                 case "-i":
 78                 case "-o":                            
 79                     break;
 80                 default:
 81                     Console.WriteLine("无效命令行参数,请重新输入!");
 82                     Console.WriteLine("命令输入格式为:exeName.exe -[命令代号] [命令对应内容],命令可以无序使用" +
 83                         ",但必须有-i -o命令。\n想要了解更多,请在输入exeName.exe后输入-help命令");                                   
 84                     break;
 85             }
 86         }
 87     }
 88 
 89     //fileOperate.wordTimes(); 
 90     /*
 91     fileOperate.charNumber();
 92     fileOperate.wordNumber();
 93     fileOperate.lineNumber();
 94     fileOperate.wordTimes();
 95     */
 96     fileOperate.closeFiles();//关闭文件流
 97     writeFile.Close();
 98     Console.WriteLine("The result has been saved to the file, please check");
 99     Console.ReadKey();
100 }
main

 

 

 

 


 

八、代码说明  

 1 /// <summary>
 2 /// 判断一个字符串是否是单词
 3 /// </summary>
 4 private bool isWords(string word)
 5 {
 6     char[] ch = word.ToCharArray();//将单词转换成字符数组
 7     if(ch.Length < 4)
 8     {
 9         return false;
10     }
11     else
12     {
13         for(int i = 0; i < word.Length; i++)
14         {
15             if(i < 4)
16             {
17                 //如果前四个字符不为字母,就不是单词
18                 if(!((ch[i] >= 'a' && ch[i] <= 'z') || (ch[i] >= 'A' && ch[i] <= 'Z')))
19                 {
20                     return false;
21                 }
22             }
23             else
24             {                        
25                 //如果第五个字符开始出现非字母、数字,就不是单词
26                 if(!((ch[i] >= 'a' && ch[i] <= 'z') || (ch[i] >= 'A' && ch[i] <= 'Z') || (ch[i] >= '0' && ch[i] <= '9')))
27                 {
28                     if(i == word.Length - 1)
29                     {
30                         if(ch[i] == ',' || ch[i] == '.' || ch[i]=='!')//若最后一个字符为","、"."、"!"前面也应该是单词
31                         {
32                             return true;
33                         }
34                     }
35                     return false;
36                 }
37             }
38         }
39         return true;
40     }
41 }

 

  此方法请参照上面的流程图,不再赘述

 1 /// <summary>
 2 /// 统计每个单词出现的次数
 3 /// </summary>
 4 public Dictionary<string, int> wordTimes(int number)
 5 {
 6     reader.BaseStream.Seek(0, SeekOrigin.Begin);//将文件重新读取一遍
 7     string temp;//临时的字符串变量
 8     string[] tempArray;//临时的字符串数组,用于存储每一行的字符串数组
 9     List<string> wordList = new List<string>();//存储单词的动态链表
10     //将单词存储到List集合中
11     while((temp=reader.ReadLine())!=null)
12     {
13         tempArray = temp.Split(' ');
14         for(int i = 0; i < tempArray.Length; i++)
15         {
16             if(isWords(tempArray[i]))
17             {
18                 if (tempArray[i].EndsWith(".") || tempArray[i].EndsWith(",") || tempArray[i].EndsWith("!"))//这里将英语标点符号的特性处理掉
19                 {
20                     char[] tempCharArray1 = tempArray[i].ToCharArray();
21                     tempArray[i] = new string(tempCharArray1, 0, tempCharArray1.Length - 1);//用字符数组的起始位置和长度创建字符串
22                 }
23                 wordList.Add(tempArray[i]);
24             }
25         }
26     }
27     int count;//临时变量count记录单词出现的次数
28     Dictionary<string, int> wordsAndTimes = new Dictionary<string, int>();//用于存储单词以及他们的次数
29     for(int i = 0; i < wordList.Count; i++)
30     {
31         count = 1;//单词本身就已经出现了一次
32         temp = wordList[i];
33         for(int j = i+1; j < wordList.Count; j++)
34         {
35             if(temp.ToLower().Equals(wordList[j].ToLower()))//不区分大小写的比较
36             {
37                 count++;
38             }
39         }
40         try
41         {
42             wordsAndTimes.Add(temp, count);//添加元素
43         }
44         catch//主要处理重复的情况
45         {
46             continue;
47         }
48     }            
49     //使用linq语句排序,以前了解过,很管用,参考https://www.cnblogs.com/wt-vip/p/5997094.html
50     var desSort = from tempElement in wordsAndTimes orderby tempElement.Value descending,tempElement.Key ascending select tempElement;
51     /*
52     foreach(KeyValuePair<string,int> keyValuePairs in desSort)//原理转换不是很清除,命名是推荐的
53     {
54         count++;
55         writeFile.WriteLine(keyValuePairs.Key + ": " + keyValuePairs.Value);
56         writeFile.Flush();//将缓冲区的文件写到基础流
57         if (count == 10)
58         {
59             break;
60         }
61     }
62     */
63     Dictionary<string, int> keyValues = new Dictionary<string, int>();
64     count = 0;
65     foreach (KeyValuePair<string, int> keyValuePairs in desSort)//原理转换不是很清除,命名是推荐的
66     {
67         count++;
68         keyValues.Add(keyValuePairs.Key, keyValuePairs.Value);
69         if (count == number)
70         {
71             //Console.WriteLine("kkk");
72             break;                    
73         }
74     }
75     return keyValues;
76 }

  此方法首先通过循环读取输入文件中单词存储到List集合中,具体过程是没循环一次就读取文件中的一行数据,通过Split()方法将其分割成字符串数组,然后对字符串数组中的每个元素进行判断,若是单词就将其存储到List集合中。然后通过双重循环对List集合中的单词进行搜索,这个过程有点像交换法排序的方式,依次比较,在这个过程中用字典对单词及其出现的次数进行存储,,内层循环一次后就会得出某个单词的频率,用try语句尝试对单词进行存储,但可能单词已经存储了,所以catch直接进行下一次循环(后面的单词出现的次数没前面想通过单词的次数多,不够准确)。最后得到了每个单词及其出现的次数的集合,由作业要求,次数优先输出,其次考虑单词的字典顺序,上网查了一下,linq语句非常适合字典排序。最后就是返回指定长度的字典对象。这里,发现了一个小知识点,就是字典不能够直接用foreach语句输出,而是用对应的KeyValuePairs作为基本的对象。

 1 /// <summary>
 2 /// 词组统计:能统计文件夹中指定长度的词组的词频,约定,词组不能跨行
 3 /// </summary>
 4 /// <param name="len"></param>
 5 /// <returns></returns>
 6 public Dictionary<string, int> wordGroup(int len)//代码复用比较多
 7 {
 8     reader.BaseStream.Seek(0, SeekOrigin.Begin);//将文件重新读取一遍
 9     string temp;//临时的字符串变量
10     string[] tempArray;//临时的字符串数组,用于存储每一行的字符串数组
11     List<string> wordList = new List<string>();//存储单词的List集合
12     while ((temp = reader.ReadLine())!=null)//读取文件
13     {
14         tempArray = temp.Split(' ');
15         for(int i = 0; i < tempArray.Length + 1 - len; i++)//+1-len的原因是后面根本没有足够的词组成词组
16         {                     
17             for (int j = 0; j < len; j++)//词组的判断
18             {
19                 if((i+j)<tempArray.Length && (!isWords(tempArray[i+j])))
20                 {                          
21                     goto end;//只要后面几个单词不是词组,那么就不满足词组定义
22                     //两个循环无法调出,迫不得已用goto语句
23                 }
24             }
25             temp = tempArray[i];
26             for (int j = 1; j < len; j++)//从第二个词开始添加
27             {
28                 temp += " ";
29                 if((i + j) < tempArray.Length)
30                 {
31                     //应该有点小bug
32                     if (tempArray[i+j].EndsWith(".") || tempArray[i+j].EndsWith(",") || tempArray[i+j].EndsWith("!"))//这里将英语标点符号的特性处理掉
33                     {
34                         char[] tempCharArray1 = tempArray[i+j].ToCharArray();
35                         tempArray[i+j] = new string(tempCharArray1, 0, tempCharArray1.Length - 1);//用字符数组的起始位置和长度创建字符串
36                     }
37                     temp += tempArray[i + j];
38                 }                        
39             }
40             wordList.Add(temp);
41         end: continue;
42         }
43     }
44     int count;//临时变量count记录单词出现的次数
45     Dictionary<string, int> wordsAndTimes = new Dictionary<string, int>();//用于存储单词以及他们的次数
46     for (int i = 0; i < wordList.Count; i++)
47     {
48         count = 1;//单词本身就已经出现了一次
49         temp = wordList[i];
50         for (int j = i + 1; j < wordList.Count; j++)
51         {
52             if (temp.Equals(wordList[j]))//约定词组区分大小写
53             {
54                 count++;
55             }
56         }
57         try
58         {
59             wordsAndTimes.Add(temp, count);//添加元素
60         }
61         catch//主要处理重复的情况
62         {
63             continue;
64         }
65     }
66     //使用linq语句排序,以前了解过,很管用,参考https://www.cnblogs.com/wt-vip/p/5997094.html
67     var desSort = from tempElement in wordsAndTimes orderby tempElement.Value descending, tempElement.Key ascending select tempElement;
68     Dictionary<string, int> keyValues = new Dictionary<string, int>();
69     //count = 0;
70     foreach (KeyValuePair<string, int> keyValuePairs in desSort)//原理转换不是很清除,命名是推荐的
71     {
72         //count++;
73         keyValues.Add(keyValuePairs.Key, keyValuePairs.Value);
74         /*
75         if (count == number)
76         {
77             //Console.WriteLine("kkk");
78             break;
79         }
80         */
81     }
82     return keyValues;
83 }

  其实wordGroup()方法实际上只是在wordTimes()的基础上扩展了一下,很多地方都直接复用了wordTimes()的代码,在此之说明不同之处:应作业需求,词组的长度是可变的,即通过传入的参数确定词组的长度,同样的遍历每行元素,不过在遍历每个元素的同时,在下标不越界的条件下,通过循环将后面len - 1个元素也遍历一次,若发现后面元素中有非单词的字符串,则以这个元素开头得不到相应的词组,若都满足,那么就将其存入List的集合中,后面操作与wordTimes()方法大致一样。


 

九、单元测试

charNumber()方法的单元测试

 

   设计思路:由于每个方法都有返回值,故创建一个FileOperate对象,然后调用该方法,进行测试即可,其它单元测试见下

 

  

   

 

  


 

十、说明

  基本功能的实现:

  

 

  

  健壮性:

  

  基本实现了错误提示功能;

  新添功能: 仿照像cmd、matlab等等中的help命令:

  

 

  获得帮助路径不唯一:

  


 

十一、总结

  通过构建之法学习到的相关内容以及结对项目的实践经历,深刻认识到团队合作中方方面面的重要性,比如代码规范、团队交流、代码合并、单元测试等等都对项目至关重要,通过本次与搭档一起共同解决项目的过程中,学到了相互沟通、任务分配、规范管理等技巧,从这次两人结对编程映射出了将来参加工作的团队合作,总的来说收获蛮大的。此次解决项目,我认为是1 + 1 < 2,因为两人刚开始合作,需要时间去磨合,我相信在后面,做的越多,1 + 1 >> 2 ! 

 

转载于:https://www.cnblogs.com/haveadate/p/10652689.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值