软件工程第二次作业
软件工程第二周作业
目录:
1.需求分析
2.基本设计
3.代码说明
4.测试运行
5.重难点知识
6.缺点与改进
7.词频统计psp
1.需求分析
作业链接:https://edu.cnblogs.com/campus/nenu/SWE2017FALL/homework/922
作业中的老五有四种需求,应该分别对应四个功能。据分析,这四个功能分别对应了输入的三种情况,功能一输入了三个参数,对指定文本文件进行词频统计,功能二三输入了两个参数,对指定文本文件和文件夹中全部文本文件进行词频统计,功能四(只实现了作业需求中“或”字之后的内容)输入一个参数即可运行,对接下来的手写输入进行词频统计。
2.基本设计
由需求分析可以得到,可以通过对输入参数个数的判断来决定执行某一程序段。
对于功能一,输入三个参数如wf –s test.txt,获取最后一个参数作为文件名,直接为相对路径,进行打开文件读取文件操作,进行词频统计。
对于功能二,输入两个参数如wf war_and_peace,即为可执行文件名和文本名(无扩展名),我的想法是要用字符串拼接,把文本名补全,变为相对路径在当前目录下搜索文件、打开文件和对其进行词频统计。
对于功能三,输入两个参数wf folder,是可执行文件名和文件夹名,为了和功能二区分开来,我加入了第二个参数是否为”folder”字符串的判断,是则执行功能三的程序。功能三用到了文件夹的遍历;还有获取当前文件夹的路径,再利用字符串拼接制造出完整的文本文件路径,分别进行词频统计。
对于功能四,只有一个参数wf,回车之后在下一行手动输入文本,写入文件,再读取文件进行词频统计。
3.代码说明
地址:https://git.coding.net/immixiaomi/wf.git
首先定义了单词类和字符置换函数,其中置换函数会在路径获取的时候用到
class Seword //定义单词类,字符型数组a[15]记录单词,整形变量num记录出现次数 { public: int num; char a[15]; }; Seword W[N]; //把文章存在W[N]里 void replaceChar(char *string, char oldChar, char newChar) //字符置换函数 { int len = strlen(string); int i; for (i = 0; i < len; i++) { if (string[i] == oldChar) { string[i] = newChar; } } return; }
主函数,argc记录参数个数,argv记录参数内容
/*******主函数,利用argc记录输入参数个数,利用数组argv[]记录参数内容*******/ int main(int argc, char *argv[])
首先判断参数个数,功能一为参数个数是3的情况,开始执行以下代码,利用在vs环境下用strncpy_s进行文件名串的获取,接下来是打开文件。其中用到了tolower()函数,在c语言中可以将大写转化成小写,头文件是ctype.h。
/*******功能一:当输入参数为三个的时候,即对应可输入>wf -s test.txt的需求*******/ if (argc==3) { int count = 1; //定义一个类 int i, k, m, n, temp, j = 0; char b[15]; //和a交换的数组 FILE *fp1 = NULL; char chh; //从文件中读取字符 int flag = 0; //是否有单词的标志位 char filename1[20]; for (i = 0; i < N; i++) //将出现次数设置为1 { W[i].num = 1; } i = 0; strncpy_s(filename1, argv[2], 20); errno_t errr; if ((errr = fopen_s(&fp1, filename1, "r")) != 0)//打开文件 { printf("error opening!"); exit(0); } while (!feof(fp1))//读取文件 { chh = fgetc(fp1); W[i].a[j] = '\0'; if (chh >= 65 && chh <= 90 || chh >= 97 && chh <= 122) //识别单词 { W[i].a[j] = tolower(chh); //小写转换大写 j++; flag = 1; } else if (chh == ' '&&flag == 1) { flag = 0; j = 0; n = i; i++; count++; if (n >= 1) //单词跟之前比较,如果相同就使次数+1 { for (m = 0; m < n; m++) { if (strcmp(W[n].a, W[m].a) == 0)//单词查重复 { W[m].num++; i = i - 1; count--; } } } } } //根据单词出现次数进行排序 for (n = 0; n < i - 1; n++) { k = n; for (j = n + 1; j < i; j++) if (W[j].num > W[k].num) { k = j; temp = W[k].num; W[k].num = W[n].num; W[n].num = temp; strcpy_s(b, 15, W[k].a); strcpy_s(W[k].a, 15, W[n].a); strcpy_s(W[n].a, 15, b); } } //输出部分 printf("total\t\t%d\n\n", count); if (count < 10) //判断是否总次数小于十个,分情况输出 { for (n = 0; n < count; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } else { for (n = 0; n <= 9; n++) //大于十个的情况 { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } }
功能二和功能三都是两个参数,判断执行哪个功能要看第二个参数是啥,如果是“folder”则为功能三,否则是功能二。下面先说功能三。
功能三用到了字符串置换,把直接查找到的路径中的’\’转化为‘/’,并且需要把双斜线换成反向的双斜线,在此感谢高远博同学写的一篇随笔http://www.cnblogs.com/gaoyb348/p/7534845.html提到了遍历文件夹,我以前从来没接触过这个知识。然后进行字符串拼接补全路径。
然后输出每一个作品名,分别对每个作品名的作品进行词频统计。
/*******当输入参数为两个的时候,对应两种需求*******/ else if (argc == 2) { /************功能三:如果第二个参数为folder则执行以下,对应>wf folder的需求**********/ if (!strcmp(argv[1], "folder")) { char oldChar = '\\'; char newChar = '//'; //确定当前目录路径时,需要进行置换,写成两个反斜线是因为是转义字符 char *buffer; if ((buffer = _getcwd(NULL, 0)) == NULL) //确定当前路径 { perror("getcwd error"); } else { struct _finddata_t files; int File_Handle; int i = 0; replaceChar(buffer, oldChar, newChar); //在这将'\'替换为'/ ' buffer = strcat(buffer, "/folder/*.txt"); //拼接为真正的路径 File_Handle = _findfirst(buffer, &files); if (File_Handle == -1) { printf("error\n"); return 0; } do { printf("%s \n", files.name);//每个作品先输出作品,再输出统计结果 int count = 1; int i, k, m, n, j = 0; char b[15]; //和a交换的数组 FILE *fp1; char chh; //从文件中读取字符 int flag = 0; //标志空格后面是否有单词 int temp; //排序时交换用 //首先将出现次数均设置为1 for (i = 0; i < N; i++) { W[i].num = 1; } i = 0; //打开文件 errno_t errr; if ((errr = fopen_s(&fp1, files.name, "r")) != 0) { printf("error opening!"); exit(0); } //读取文件 while (!feof(fp1)) { chh = fgetc(fp1); W[i].a[j] = '\0'; if (chh >= 65 && chh <= 90 || chh >= 97 && chh <= 122) { W[i].a[j] = tolower(chh); //大小写转换 j++; flag = 1; } else if (chh == ' '&&flag == 1) { flag = 0; j = 0; n = i; i++; count++; if (n >= 1) //单词跟之前比较,如果相同就使次数+1 { for (m = 0; m < n; m++) { if (strcmp(W[n].a, W[m].a) == 0) { W[m].num++; i = i - 1; //单词查重复的判断 count--; } } } } } //根据单词出现次数进行排序 for (n = 0; n < i - 1; n++) { k = n; for (j = n + 1; j < i; j++) if (W[j].num > W[k].num) { k = j; temp = W[k].num; W[k].num = W[n].num; W[n].num = temp; strcpy_s(b, 15, W[k].a); strcpy_s(W[k].a, 15, W[n].a); strcpy_s(W[n].a, 15, b); } } //输出部分 printf("total\t\t%d\n\n", count); if (count < 10) { for (n = 0; n < count; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } else { for (n = 0; n <= 9; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } i++; } while (0 == _findnext(File_Handle, &files)); _findclose(File_Handle); free(buffer); //清空路径申请的buffer空间 } return 0; }
功能二,第二个参数并没有要求输入扩展名,这就要求我们要将扩展名拼接在上边,从而得到相对路径。
/*********功能二:在两个参数的情况下,直接输入文件名打开当前目录下文件,对应输入>wf test的情况***********/ else { int count = 1; int i, k, m, n; char b[15]; //用来和a交换的数组 FILE *fp1 = NULL; //定义文件 char chh; //从文件中读取字 int flag = 0; //标志位 int j = 0; int temp; //排序时交换变量 char filename1[30]; //将出现次数均设置为1 for (i = 0; i < N; i++) { W[i].num = 1; } i = 0; //打开文件 strncpy_s(filename1, argv[1], 30); //字符串拼接,把.txt拼接为路径 strcat_s(filename1, 30, ".txt"); errno_t errr; if ((errr = fopen_s(&fp1, filename1, "r")) != 0) { printf("error opening!"); exit(0); } //读取文件 while (!feof(fp1)) { chh = fgetc(fp1); W[i].a[j] = '\0'; if (chh >= 65 && chh <= 90 || chh >= 97 && chh <= 122) { W[i].a[j] = tolower(chh); //转成小写 j++; flag = 1; } else if (chh == ' '&&flag == 1) { flag = 0; j = 0; n = i; i++; count++; if (n >= 1) //单词跟之前比较,如果相同就使次数+1 { for (m = 0; m < n; m++) { if (strcmp(W[n].a, W[m].a) == 0) { W[m].num++; i = i - 1; //单词查重复的判断 count--; } } } } } //根据单词出现次数进行排序 for (n = 0; n < i - 1; n++) { k = n; // for (j = 0; j < i; j++) for (j = n + 1; j < i; j++) if (W[j].num > W[k].num) { k = j; temp = W[k].num; W[k].num = W[n].num; W[n].num = temp; strcpy_s(b, 15, W[k].a); strcpy_s(W[k].a, 15, W[n].a); strcpy_s(W[n].a, 15, b); } } //输出部分 printf("total\t\t%d\n\n", count); if (count < 10) { for (n = 0; n < count; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } else { for (n = 0; n <= 9; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } } }
功能四只有一个参数,所以判定argc==1,要进行手动输入文章,然后把其写入文件,再读取文件进行统计。
/********功能四:一个参数,直接手动输入写入文件并读取统计,对应第四个功能第二种情况************/ else if (argc == 1) { FILE *fp; errno_t err; char ch; if ((err = fopen_s(&fp, "test.txt", "w")) != 0) { printf("无法打开此文件\n"); exit(0); } ch = getchar(); //用来接收最后输入的回车符 while (ch != '\n') //以回车标志输入结束 { fputc(ch, fp); ch = getchar(); } fclose(fp); //关闭文件 int count = 1; int i, k, m, n; char b[15]; // 用来和a交换的数组 FILE *fp1; char chh; //从文件中读取字符 int flag = 0; //空格后面是否有单词 int j = 0; int temp; //将出现次数均设置为1 for (i = 0; i < N; i++) { W[i].num = 1; } i = 0; //打开文件 errno_t errr; if ((errr = fopen_s(&fp1, "test.txt", "r")) != 0) { printf("error opening!"); exit(0); } //读取文件内容 while (!feof(fp1)) { chh = fgetc(fp1); W[i].a[j] = '\0'; if (chh >= 65 && chh <= 90 || chh >= 97 && chh <= 122) { W[i].a[j] = tolower(chh); //识别单词 j++; flag = 1; } else if (chh == ' '&&flag == 1) { flag = 0; j = 0; n = i; i++; count++; if (n >= 1) //单词跟前面的比较,如果相同就使次数+1 { for (m = 0; m < n; m++) { if (strcmp(W[n].a, W[m].a) == 0) { W[m].num++; i = i - 1; //相同则认为是一个单词 count--; } } } } } //根据单词出现次数进行排序 for (n = 0; n < i - 1; n++) { k = n; for (j = n + 1; j < i; j++) if (W[j].num > W[k].num) { k = j; temp = W[k].num; W[k].num = W[n].num; W[n].num = temp; strcpy_s(b, 15, W[k].a); strcpy_s(W[k].a, 15, W[n].a); strcpy_s(W[n].a, 15, b); } } //输出部分 printf("total\t\t%d\n\n", count); if (count < 10) { for (n = 0; n < count; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } else { for (n = 0; n <= 9; n++) { printf("%s", W[n].a); printf("\t\t%d\n", W[n].num); } } }
4.测试运行
功能一
在功能二里我对数组的定义有问题,导致无法正确排序,大词汇量会溢出。因为我不会使用动态定义数组,正在学习中,需要另外的两天,无法赶上提交截止日期。但是词频和总词数是正确的。
词汇量少的是正确的。
功能三可以遍历文件夹,可以正确输出文件名,但是无法排序,且经过几次测试之后无法复现,所以功能三并未实现。
功能四可以实现。
5.重难点知识
我觉得这次作业的重点难点大概有:遍历文件夹、查询当前路径、字符串的拼接和vs的安全检查导致的函数名称的转换。我参考了以下博客或网站:
查找当前工作路径http://blog.csdn.net/huhongfei/article/details/38470081
Fopen_s打开文件http://blog.csdn.net/aobo516/article/details/46387615
Strcpy_s的用法http://blog.csdn.net/geekvc/article/details/22578215
C语言下遍历文件夹https://zhidao.baidu.com/question/575084429.html
修改vs中强制进行安全检查的选项http://jingyan.baidu.com/article/359911f5427b6957fe03068f.html
6.缺点和改进
这次作业没理解动态定义数组,导致了功能二和功能三的失败。我想排序算法加入剪枝可以更快速度统计。我的程序模块化做的相当差,导致代码冗长。我将在接下来的一周里做这些改正。
7.词频统计psp
分析:功能1初次试水,对时间的估计不是很准,也有一些突发状况,但功能1的代码在2和4都可以借鉴甚至可以照搬过去其中的部分,为我节省了很多时间。在功能3遇到了大问题,遍历文件夹搜了好多资料又进行了改动,最后也没有成功运行,所以远远超出预期时间。