一、项目地址
【 https://github.com/Heartbreaker16/WordCount】
二、PSP表格
PSP2.1 | PSP阶段 | 预估耗时 (分钟) |
实际耗时 (分钟) |
Planning | 计划 | 20 |
10 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 960 | 1320 |
· Analysis | · 需求分析 (包括学习新技术) | 180 | 180 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 | 0 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 360 | 540 |
· Code Review | · 代码复审 | 0 | 0 |
· Test | · 测试(自我测试,修改代码,提交修改) | 360 | 540 |
Reporting | 报告 | 180 | 570 |
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 150 | 540 |
合计 | 1160 | 1900 |
三、解题思路
本项目基于C语言实现。由于WordCount可以理解为不同功能的叠加,因此可以在C中把每种功能抽象成一个函数,在各自的函数体内实现各自的功能,本项目中已实现的功能及其思路如下:
基本功能:
1. 统计字符数:逐个扫描文本中的字符,设置count变量计数;
2. 统计单词数:逐个扫描文本中的字符,设置flag标志变量记录上一个字符的属性,当在单词字符后扫描到分隔字符时,计数;
3. 统计行数:逐个扫描文本中的字符,扫描到换行符时计数;
4. 结果写入文件:调用文件读写函数。
扩展功能:
1. 统计空行数:逐个扫描文本中的字符,设置flag标志变量记录上一行的属性。当扫描到可见字符时标记该行是非空行,此时再次扫描到换行符时不计数,反之计数;
2. 统计单词数(带有停用词表):统计单词数的拓展应用,抽取输入文本和停用词表中所有的单词,逐一比对,记录重复个数。
可以把这些函数按功能优先级顺序存储在一个结构体数组中表示成功能列表,一个结构体元素对应一个功能,它包含指向实现该功能的函数的指针和该功能的开关变量。不同函数中的相同部分可以再次抽象成一个函数模块,例如文件访问,结果输出等。main函数负责解析用户在终端的输入来打开相应的功能开关。
由于各项功能都涉及对文件和字符串的操作,因此完成该项目前简单了解了相应的库函数,查阅了一些关键函数的用法,并查阅了Git的相关操作教程。
四、程序设计实现过程
本项目中构造的函数如下:
void count_char(char* input, char* output);//统计字符数
void count_word(char* input, char* output);//统计单词数并输出
void count_line(char* input, char* output);//统计行数
void count_null(char* input, char* output);//统计空行数
void output(char* input, char* output, char* title, int count);//输出结果并写入文件
void stopList(char* input, char* stoptxt, char* output);//统计单词数(带有停用词表)
void wordsave(char** list, char* file, char* filter);//提取文本中所有的单词到二维数组
int Listsize(char* file, char* filter);//统计单词数
FILE* openfile(char* file, char* type);//文件访问
函数间的调用关系如下:
实现本项目各种功能的最核心思想就是设置标志变量和计数。统计单词数是这种思想的典型应用之一,其核心部分的流程图如下:
其他功能如统计空行数、统计带有停用词表的单词数都是这种思路的应用,其核心流程大同小异,不一一阐释。
五、代码说明
之前展示了统计单词数的函数(Listsize)流程图,现在说明一下统计不在停用词表内单词的函数代码。实现此功能需要调用两个函数。
第一个函数功能是统计所有单词的数量(Listsize):(流程图已在前面给出)
int Listsize(char *file, char *filter)//统计单词个数 { FILE *fp = openfile(file, "r"); if (fp == NULL) return -1; char ch; int count = 0; bool afterWord = false;//标记当前字符是否在单词字符后面 while (!feof(fp)) { ch = fgetc(fp); if (strchr(filter, ch) != NULL && afterWord)//当前字符是分隔符且前一个字符是单词字符 { count++; afterWord = false;//设置标记:当前字符是分隔符 } else if (strchr(filter, ch) == NULL && ch != EOF)//当前字符是单词字符 afterWord = true;//设置标记:当前字符是单词字符 } fclose(fp); if (afterWord)//文件最后一个字符是单词字符,则最后结果加一 count++; return count; }
函数原理:逐个扫描文本中的字符,设置flag标志变量记录上一个字符的属性,当在单词字符后扫描到分隔字符时计数,详见注释。
第二个函数功能是提取文本中所有的单词到二维数组(Wordsave):
void wordsave(char **list, char *file, char *filter)//提取文本中所有的单词到二维数组 { FILE *fp = openfile(file, "r"); if (fp == NULL) return; char ch; int i = 0, j = 0; bool afterWord = false;//标记当前字符是否在单词字符后面 while (!feof(fp)) { ch = fgetc(fp); if (strchr(filter, ch) != NULL && afterWord)//当前字符是分隔符且前一个字符是单词字符 { afterWord = false; //设置标记:当前字符是分隔字符 i++; //当前单词记录结束,开始记录下一单词 j = 0;//新单词从首位开始记录 } else if (strchr(filter, ch) == NULL && ch != EOF)//当前字符是单词字符 { list[i][j++] = ch;//将当前字符存储在该单词中 afterWord = true;//设置标记:当前字符是单词字符 } } fclose(fp); }
函数原理:在识别单词的基础上将单词存储在二维数组里构成文本的单词表,详见注释。
统计不在停用词表内单词的函数主体(Stoplist):
void stopList(char * filename, char *stoptext, char * txtname)//统计不在停用词表中的单词数 { int count = 0; //获取输入文件和停用词表中的单词个数 int countword = Listsize(filename, ", \n\t"), countstop = Listsize(stoptext, " "); if (countword *countstop < 0) return; //动态申请存储输入文件中所有单词的二维数组 char **wordList = (char**)malloc(countword * sizeof(char*)); for (int i = 0; i < countword; i++) wordList[i] = (char*)malloc(200); //动态申请存储停用词表中所有单词的二维数组 char **stopList = (char**)malloc(countstop * sizeof(char*)); for (int i = 0; i < countstop; i++) stopList[i] = (char*)malloc(200); //二维字符数组初始化 for (int i = 0; i < countword; i++) for (int j = 0; j < 200; j++) { wordList[i][j] = '\0'; } for (int i = 0; i < countstop; i++) for (int j = 0; j < 200; j++) { stopList[i][j] = '\0'; } wordsave(wordList, filename, ", \n\t"); wordsave(stopList, stoptext, " "); //统计重复的单词个数 for (int i = 0; i < countstop; i++) for (int j = 0; j < countword; j++) { if (strcmp(stopList[i], wordList[j]) == 0) count++; } //释放动态申请的内存空间 for (int i = 0; i<countword; i++) free(wordList[i]); free(wordList); for (int i = 0; i<countstop; i++) free(stopList[i]); free(stopList); output(filename, txtname, "单词数:", countword - count); }
函数原理:利用Listsize函数求得的单词个数动态分配单词表二维数组,再使用Wordsave函数将文本和停用词表中的单词分别记录到单词表中,然后逐一比对得到结果,详见注释。
六、测试设计过程
针对已实现的功能,考虑其运行过程中的每个分支,设计了十个测试用例。分别是针对每个函数模块的测试、集成功能的测试,以及对于用户可能的错误输入也包含在了测试用例中。
测试用例:
1.测试字符统计(-c)
2.测试单词统计(-w)
3.测试行数统计(-l)
4.测试写入结果(-o output.txt)
5.测试空行数统计(-a)
6.测试带有停用词表的单词数统计(-e stoplist.txt)
7.集成测试(-c -w -l input.c -a -e stoplist.txt -o output.txt)
8.文件错误输入(-c wronginput.c)
9.停用词表错误输入(-w input.c -e wrongstoplist.txt)
10.测试零输入()
输入文件input.c: 停用词表stoplist.txt:
将1—10测试在控制台窗口依次执行,得到运行结果:
结果均统计正确,对于用户的错误输入也有合理的反馈。
评价:测试用例覆盖全面,遍历了所有功能,满足了判定覆盖、条件覆盖和路径覆盖。同时也考虑了用户的错误输入,对程序的健壮性也进行了有效测试,总体比较满意。
七、参考链接
http://www.cnblogs.com/xinz/archive/2011/11/20/2255830.html
http://www.cnblogs.com/cxk1995/p/5800196.html
https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/