题目:统计文本中重复次数最多的前1000的单词,并按次数从高到低输出,并保存到另一个文本中
题目这里说明一下,是对英文文本进行操作,所以这里是在http://novel.tingroom.com/ertong/4017/随意下了一篇英文小说,名字叫 A 《Sweet Little Maid》
这个题目要解决两个问题,第一是如何将仅读单词然后划分开并且保存,第二是保存以后如何去重。
这里暂且创建两个结构体,一个通过结构体创建结构体数组,每一个存储一个单词,此处并不去重,然后另一个也是创建结构体数组,但是这个是字典创建,并且有计数的变量,具体结构体如下
typedef struct wcount//读入单词结构体
{
char word[20];
};
typedef struct worddic//字典结构体
{
char word[20];
int count;
};
因为这里创建的是字符数组,所以分别写一个初始化函数,字符数组初始化为空字符串,计数器初始化为0。实现如下
void init(wcount *wc,const int len)//初始化读入单词的字符串
{
for(int i=0;i < len; ++i)
{
wc[i].word[0] = '\0';
}
}
void initd(worddic *wd,int len)//初始化字典的字符串
{
for(int i=0;i < len; ++i)
{
wd[i].word[0] = '\0';
wd[i].count = 0;
}
}
然后就是对文本的操作了,打开文件,读取数据关闭文件。在当中要计算一下文本大小,以此来选择动态内存的调用大小
void dofile(const char *path,const char *despath)//文件操作
{
FILE *fp = fopen(path,"rb");
assert(fp != NULL);
fseek(fp,0,SEEK_END);//光标移至文本尾
int len = ftell(fp)/sizeof(char);//计算大小
fseek(fp,0,SEEK_SET);//恢复光标,至文本头
wcount *wc = (wcount *)malloc((len/4)*sizeof(wcount));
worddic *wd = (worddic *)malloc((len/4)*sizeof(worddic));//动态内存创建
}
因为题目是将处理结果保存至另一个文本中,所以这里有目标文件的存储地址,还有要存在哪里,这里需要说的就是,使用fseek()函数将光标移动到文本结尾,计算完文本大小,容易忘记将光标移动回文本头,会让接下来读文件内容时无法读取
接下来时读取、划分、存储单词,代码实现如下
void saveword(wcount *wc,char word ,FILE *fp)//将单词读取至结构体的word中
{
int i = 0;
int j = 0;
while(fread(&word,sizeof(char),1,fp) > 0)
{
if(word == '\n' || word == '\r')//跳过换行
{
continue;
}
if(jud(word))//字母赋值给单词结构体
{
wc[i].word[j] = word;
j ++;
}
else if(!jud(word))
{
wc[i].word[j] = '\0';//将上述单词加结尾标记,使其成为字符串
if(strlen(wc[i].word) == 0)//处理遇到连续非字母字符,直接跳过
{
continue;
}
else //一个单词读取完成,结构体跳至下一个,存储单词的word数组脚标重置
{
++ i;
j = 0;
}
}
}
wc[i].word[j] = '\0';//最后一个单词变为字符串
}
这里本来是通过库函数isalpha()函数来判断当前读入字符是否为字母,但是在运行时候总是断言失败,所以就自行编写jud()函数进行简易判断。
这里说一下大概的存储思路,若是当前字符为字母,则将他赋值给当前结构体的word变量的0号下表,然后下标加一,若是当前字符不是字母,则把当前word变量最后一个赋值为’\0’,使其变为字符串,若是当前非字母,并且当前结构体的word为空串,说明遇到连续非字符,所以当前word变量不进行赋值等待并且字母字符,若是当前非字母,并且当前word不是空串,说明一个单词读取结束,所以word变量跳至下一个结构体的word变量,并且下标计数器恢复为0,最后的最后把最后一个单词也变为字符串。这样就把单词全都存入结构体中了。
接下来就是字典的创建了,实现代码如下
void savedic(const wcount *wc,worddic*wd)//字典创建
{
int i = 0;
int j = 0;
int MAX = 0;
for(i = 0;strlen(wc[i].word)!=0;++i)
{
for(j = 0;j < MAX;++j)
{
if(strcmp(wc[i].word,wd[j].word)!=0)
continue;
break;
}
if(j == MAX)
{
strcpy(wd[j].word,wc[i].word);
++ MAX;
}
}
}
写循环,判断条件为若是单词结构体的word非空串,就继续循环,i++,定义一个MAX变量来确定字典的边界,因为每有一个新单词时候,边界就要加一,所以这里MAX是动态的,在边界内对每个进来的单词进行比较,若是不相等,则继续比较,一旦相等,就跳出此趟比较。祠堂比较结束,最里层循环外有一个判断语句,对跳出来的下标与边界进行比较,若是相等,说明此趟比较已经比较结束,并且没有发现当前词典中与该单词相等的单词,说明是新单词,所以将该单词加入到词典中,边界加一,循环结束,词典创建结束。
词典创建结束,剩下的工作就好办了,对照词典进行词频统计实现代码如下
void count(const wcount *wc,worddic *wd)//对照字典进行计数
{
for(int i = 0;strlen(wc[i].word)!=0;++i)
{
for(int j = 0;strlen(wd[j].word)!=0;++j)
{
if(strcmp(wc[i].word,wd[j].word)==0)
{
wd[j].count ++;
break;
}
}
}
}
若是单词与词典中一个单词相同,该单词的计数器加一,跳出,后续单词不进行比较,以此加快效率
然后就是词频排序了,这个就是简单的排序,不做多说明
void countsort(worddic *wd)//次数排序
{
int tmp;
char ttmp[50];
for(int i = 0;strlen(wd[i].word)!=0;++i)
{
for(int j = i+1;strlen(wd[j].word)!=0;++j)
{
if(wd[i].count < wd[j].count)
{
tmp = wd[i].count;
wd[i].count = wd[j].count;
wd[j].count = tmp;//次数交换
strcpy(ttmp,wd[i].word);
strcpy(wd[i].word,wd[j].word);
strcpy(wd[j].word,ttmp);//字符串交换
}
}
}
}
若是定义一个保存字符串的中间变量,定义一个保存词频的中间变量,然后把词频大的换到最前边
然后就是结果打印了
读取前一千的词典结构体数组的值,然后输出至结果文本中,代码实现如下
void write1000(const char *despath ,worddic *wd)//写前一千的单词,并保存至目标路径
{
FILE *fd = fopen(despath,"wb");
assert(fd != NULL);
for(int i = 0;i < 1000;++i)
{
fprintf(fd,"%s,%d",wd[i].word,wd[i].count);
fputs("\n",fd);
}
fclose(fd);
}
这个小项目也算完成了,不过此项目的代码目前还没有进行修改简化,后续有时间简化提炼之后,我会把第二版也发上来。
这个小程序还需要一个小操作就是小说下载后,此小说结尾是直接结尾,所以我在后边加了一个空格,方便读入操作