福大软工1816 · 第二次作业 - 个人项目
一、Github项目地址:https://github.com/a1831530848/personal-project
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 120 | 900 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 | 20 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 240 | 420 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 10 | 10 |
· Size Measurement | · 计算工作量 | 10 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 605 | 1665 |
三、解题思路
刚刚拿到题目的时候,我并不懂得如何输入输出文件,所以拿出了谭浩强的书以及百度进行学习,才明白一些文件的相关操作。一边学习文件的操作,一边思考如何进行统计与输出,并在网上参考类似问题别人的思路。偶然在大佬的博客中看到了自动机之类的高端词汇,心血来潮百度了一番,想尝试一下,不过由于感觉太难只好打消这个念头。限于编码能力与阅码能力的不足,网上很多代码看不太懂,自己也难以实现,只好放弃。最后,我还是选择了比较low的暴力解法。如下便是我的解题思路。
1.统计文件字符数
只需一个字符一个字符地读取文件,每读一个就加一,直到结束。
2.统计文件的单词总数
用某个变量表示当前连续读取的长度,变量<4时读到字母就加一,变量>=4时读到字母或数字就加一,其余情况变量都清零。每次清零前变量若>=4则单词总数加一。
3.统计文件的有效行数
每次读取到换行符时,若该行出现过非空白字符则有效行数加一,其中用某个变量来判别该行是否出现过非空白字符。
4.输出出现频率最高的10个单词
使用结构体数组,结构体存放单词名和出现次数。读完一个单词就遍历判断其是否在已有的结构体中,有则次数加一,否则存放在一个新的结构体中,次数取1。文件读取完之后用冒泡排序的方法,使出现次数最多(若一样多则字典序优先)的10个浮到最顶端,输出这10个。
四、设计实现过程
首先是把欲读取的文件名存入filename,接下来用如下四个函数分别实现对应的功能:
Characters(filename);
Words(filename);
Lines(filename);
TopWords(filename);
相应的测试:由于还不太会使用单元测试,便手动输入了诸多测试,如下是其中一个测试用例:
五、性能分析
针对如上的测试用例,由VS2017的性能分析工具自动生成的性能分析图。
六、关键代码
如下仅展示部分关键代码。
首先是主函数,先判断文件是否错误,正确才继续运行。
int main()
{
FILE *FP=NULL;
char filename[100];
printf("请输入欲读取的文件名:");
scanf("%s",filename);
if((FP=fopen(filename,"r"))==NULL)
{
printf("打开文件错误");
return 0;
}
fclose(FP);
Characters(filename);
Words(filename);
Lines(filename);
TopWords(filename);
return 0;
}
统计字符数,一个一个读取直到结束:
while(!feof(fp))
{
ch=fgetc(fp);
if(ch==-1)break;
characters++;
}
统计单词总数,只需要考虑当前读到的是字母还是数字还是其他:
while(!feof(fp))
{
ch=fgetc(fp);
if(ch==-1)
{
if(length>=4)words++;
break;
}
if(ch>=65&&ch<=90)ch=ch+32;
if(ch>=97&&ch<=122)length++;
else if(ch>=48&&ch<=57&&length>=4)length++;
else
{
if(length>=4)words++;
length=0;
}
}
统计有效行数,遇到换行符需根据该行是否出现过非空白符来判断有效行数是否需要加一:
while(!feof(fp))
{
ch=fgetc(fp);
if(ch==-1)
{
if(changeline==0)lines++;
break;
}
if(ch=='\n'&&changeline==0)
{
changeline=1;
lines++;
}
if(changeline==1&&ch!='\n'&&ch!=' '&&ch!=' ')changeline=0;
}
统计出现频率最高的10个单词,首先把单词与次数用结构体存好,再用冒泡排序把频率最高的10个(若不足10个则全部排序)浮到顶端:
while(!feof(fp))
{
ch=fgetc(fp);
if(ch==-1)
{
if(length>=4)
{
flag=0;
now[length]='\0';
for(i=0;i<sum;i++)
{
if(strcmp(w[i].name,now)==0)
{
w[i].count++;
flag=1;
break;
}
}
if(flag==0)
{
strcpy(w[sum].name,now);
w[sum].count++;
sum++;
}
}
break;
}
if(ch>=65&&ch<=90)ch=ch+32;
if(ch>=97&&ch<=122)
{
now[length]=ch;
length++;
}
else if(ch>=48&&ch<=57&&length>=4)
{
now[length]=ch;
length++;
}
else
{
if(length>=4)
{
flag=0;
now[length]='\0';
for(i=0;i<sum;i++)
{
if(strcmp(w[i].name,now)==0)
{
w[i].count++;
flag=1;
break;
}
}
if(flag==0)
{
strcpy(w[sum].name,now);
w[sum].count++;
sum++;
}
}
length=0;
}
}
for(j=0;j<10&&j<sum;j++)
for(i=sum-1;i>j;i--)
{
if((w[i].count>w[i-1].count)||((w[i].count==w[i-1].count)&&(strcmp(w[i].name,w[i-1].name)==-1)))
{
t=w[i].count;
w[i].count=w[i-1].count;
w[i-1].count=t;
strcpy(now,w[i].name);
strcpy(w[i].name,w[i-1].name);
strcpy(w[i-1].name,now);
}
}
七、心得体会
感觉这次的实战任务我只是完成了能够实现功能的代码,至于封装什么的我并没有做到。这次的任务困难重重,首先是VS2017不知道为什么一直安装不成功,搞了一晚上,尝试了各种各样的解决方案,最终以失败告终,无奈过后先借舍友的电脑来使用VS2017。接着是github,搞了半天还是没懂如何上传,所以在写代码的过程中就没有写一点就提交一次。最后是限于水平与时间的不足,使用的方法有点low,一些任务诸如封装还有单元测试也并没有实现,与别人的差距很明显地体现了出来。总的来说,我对这次的任务完成的并不满意。要学的东西还有很多,在软件工程实践上还需要投入更多的时间。