一、引言
最近花了很多时间来思考老师给我们布置的这道作业题,感觉学习编程以来算是第一次接触这种比较复杂的题目。完整地做完之后收获颇丰,在这里写一篇blog记录一下所思所想,留待日后回看反省。第一次写blog,质量不佳请多宽待。
1.1题目概述
完成以下任务:
给定一个(英文)文本文件,统计其中各个单词出现的次数,并按照出现次数从大到小的顺序以小写字母列出(次数相同的按字典序排序)。
提交要求:提交源代码,编译链接后的可执行文件count.exe可以接受任何文本文件,并输出结果。如在命令行中执行以下指令:
>count.exe xxx.txt
1.2题目分析
简要分析可以发现题目的要求可以分成三个阶段:文件读入、统计单词次数、和打印结果。再进一步分析的话还可以细分出两个阶段,就是在文件读入之后将所得字符串拆分成若干个单词的操作及统计完成之后的排序操作。
由于在刚接触这道题的时候还并没有具体学习如何从文件中获取输入,老师只给我们提示了要使用带参数的main函数。于是我选择先暂时放下这个部分,先行实现后续部分,最后再研究文件读入的部分。
二、解题过程
2.1 单词存储
这里我选择了建立一个结构体来存放单词的相关信息:
typedef struct word{
char words[MAX];
int count;
}word;
其中字符数组words用于存放单词,count用于统计这个单词出现的总次数,方便后续进行排序,对于不同单词的存放我选择了使用结构体数组。
2.2 分割字符串
这一阶段显然是整个程序最重要的部分之一。最初我并没有考虑到文本中可能含有类似"they’re"这种带缩写单词的情况,单纯地认为只要去掉空格、换行符以及各种标点符号之后,剩下的就都是单词了,于是我借助了isalpha函数来过滤掉所有非字母字符。
字符串处理初版
int handle(FILE *fp,word *w[])
//将文件中的字符处理成单词(大写将全部转换成小写
{
int i=0,n=0;
char ch;
char single[MAX]={
0};
while((ch=fgetc(fp))!=EOF)
{
if(!isalpha(ch))
continue;//遇到字符时跳过本次循环
i=0;
while(isalpha(ch))
{
if(ch>='A'&&ch<='Z')//将大写字母全部转换成小写字母
ch+=32;
single[i++]=ch;
ch=fgetc(fp);
}
single[i]='\0';//手动添加'\0'
w[n]=(word*)malloc(sizeof(word));
if(first(w,single,n)==-1)//first函数用于判断single是否第一次出现
n++;//n代表不重复的单词的总数
putin(single,w,first(w,single,n),n-1);//putin函数用于将该字符串的相关数据保存进w中
}
return n;
}
后续在和同学的讨论中注意到老师提供的文本中有不少单词都是以"they’ll"或者"hand-in-hand"这类含单引号或者连字符的形式出现的,询问了之后老师给出含单引号的缩写删去单引号后续部分,连字符不作处理的答复。于是我对代码进行了优化,添加了额外的情况。
字符串处理升级版
int handle(FILE *fp,word *w[])
//将文件中的字符处理成单词(大写将全部转换成小写)并存入结构体中
{
int i=0,n=0;
char ch,bh=0;//bh用于处理单词中含有单引号的情况
char single[MAX]={
0};
while((ch=fgetc(fp))!=EOF)
{
if(!isalpha(ch))//遇到下一个非字母字符后,重置bh
bh=0;
if(isalpha(ch)&&bh!=0)//当bh不为零且ch是字母时,说明当前字符处于单引号之后,跳过本次循环
continue;
if(!isalpha(ch)&&ch!='-')//遇到非字母字符且该字符不为连字符时跳过本次循环
continue;
i=0;
while(isalpha(ch)||ch=='-')
{
if(ch>='A'&&ch<='Z')//将大写字母全部转换成小写字母
ch+=32;
single[i++]=ch;
ch=fgetc(fp);
}
single[