代码改变世界

词频分析项目报告

2018-03-30 21:45  ccj1998  阅读(937)  评论(1编辑  收藏  举报
  • 要求
  1. 对源文件(*.txt,*.cpp,*.h,*.cs,*.html,*.js,*.java,*.py,*.php等)统计字符数、单词数、行数、词频,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。
  2. 使用性能测试工具进行分析,找到性能的瓶颈并改进
  3. 对代码进行质量分析,消除所有警告
  4. 设计10个测试样例用于测试,确保程序正常运行(例如:空文件,只包含一个词的文件,只有一行的文件,典型文件等等)
  5. 使用Github进行代码管理
  6. 撰写博客

基本功能

  1. 统计文件的字符数
  2. 统计文件的单词总数
  3. 统计文件的总行数
  4. 统计文件中各单词的出现次数
  5. 对给定文件夹及其递归子文件夹下的所有文件进行统计
  6. 统计两个单词(词组)在一起的频率,输出频率最高的前10个。

功能模块

  • 遍历目录

                 调用函数listDir,参数path为需要读取的路径,变量childpath为文件名

void listDir(char *path){
    DIR      *pDir=NULL;
    struct dirent  *ent=NULL;
    int       i=0;
    char      childpath[512],ch;
    
    pDir=opendir(path);
    memset(childpath,0,sizeof(childpath));
    
    while((ent=readdir(pDir))!=NULL){
        if(ent->d_type & DT_DIR){
            if(strcmp(ent->d_name,".")==0||strcmp(ent->d_name,"..")==0)continue;
            sprintf(childpath,"%s%s/",path,ent->d_name);
            listDir(childpath);
        }
        else{
            sprintf(childpath,"%s%s",path,ent->d_name);
                
            puts(childpath);

            if((fin=fopen(childpath,"r"))==NULL){
                fprintf(fout,"cannot open this file\n");
                exit(0);
            }
        
        
            while((ch=fgetc(fin))!=EOF){
                if(ch>=32&&ch<=126){         //counting the number of character
                    count_character++;  
                }
                if(ch=='\n'){
                    count_line++;
                } 
                wordjudge(ch);
            } 
        
            preword=NULL;
            wordstore[0]=wordstore[1]='\0'; 
            count_line++; 
            fclose(fin);
        }
    }
}
  • 判断单词

              调用函数wordjudge,对输入的字符判断其能否构成一个单词。若能,则将形成的字符进行下一步处理

void wordjudge(char ch){
    int i;
    char dest[WORDLEN]={0};
    if(ch>='A'&&ch<='Z'||ch>='a'&&ch<='z'||ch>='0'&&ch<='9'){          
        i=wordstore[0];
        if(i<WORDLEN-2){
            i=++wordstore[0]; 
            wordstore[i]=ch;
            wordstore[i+1]='\0';
        }
        else{
        //    printf("the word is too long!\n");
        }
    }
    else if(wordstore[0]) {
        for(i=1;i<=4&&wordstore[i]&&(wordstore[i]>='A'&&wordstore[i]<='Z'||wordstore[i]>='a'&&wordstore[i]<='z');i++);
        if(i==5){
            strncpy(dest,wordstore+1,strlen(wordstore)-1);
            dest[strlen(dest)]='\0';
            wordoperation(dest);
            count_word++;
        }
        wordstore[0]=0;
        wordstore[1]='\0';
    }
}
  • 单词处理

                 对要处理的单词进行标准化,对标准化后的单词哈希处理,存入单词结构体哈希表中。同时也顺带将当前单词和前一个单词组成的词组存入词组哈希表中

void wordoperation(char* newword){
    int i,t,j,k,h=0,g=0;
    char c,newwordc[WORDLEN];
    strcpy(newwordc,newword);
    t=(int)strlen(newword);
    c=*(newwordc+t-1);
    for(;c>='0'&&c<='9';t--){
        newwordc[t]='\0';
        c=*(newwordc+t-1);
    }
    strlwr(newwordc);
    /*The following centences are to store word*/
    j=strlen(newwordc);
    for(k=1;k<j&&k<10;k++){
        h=(newwordc[k-1]+(h*36))%WORDNUM;
    }
    while(Hashw[h].frequency&&strcmp(Hashw[h].after_deal,newwordc)){
        h=(h+1)%WORDNUM;
    }
    if(Hashw[h].frequency){
        Hashw[h].frequency ++;
        if(strncmp(Hashw[h].content ,newword,51)>0){
            strcpy(Hashw[h].content  ,newword);
        }
    }
    else{
        Hashw[h].frequency =1;
        strcpy(Hashw[h].content,newword);
        strcpy(Hashw[h].after_deal ,newwordc); 
    }
    /*The following centences are to store word group*/
    if(preword){
        for(i=0;i<4;i++){
            g=(g*36+preword->after_deal[i])%GROUPNUM;
        }
        for(i=0;i<4;i++){
            g=(g*36+newwordc[i])%GROUPNUM; 
        }
        while(Hashg[g].frequency && 
        (strncmp(Hashg[g].firstword->after_deal,preword->after_deal,51)||strncmp(Hashg[g].secword->after_deal,newwordc,51))){
            g=(g+1)%GROUPNUM;
        }
        if(Hashg[g].frequency){
            Hashg[g].frequency ++;
        }
        else{
            Hashg[g].frequency =1;
            Hashg[g].firstword =preword;
            Hashg[g].secword =&Hashw[h];
        }
    
    }
    preword=&Hashw[h];
}
  • 寻找前十单词和词组

                  遍历哈希表,找出前十单词和词组

void toptenw(void){
    word tenw[10]={0};
    int j,m,n;
    for(j=0;j<WORDNUM;j++){
        if(Hashw[j].frequency ){
            for(m=9;m>=0&&(!tenw[m].frequency||Hashw[j].frequency>tenw[m].frequency);m--);
            if(m+1<=9){
                for(n=9;n>m+1;n--){
                    tenw[n]=tenw[n-1];
                }
                tenw[m+1]=Hashw[j];
            }
        }
    }
    for(j=0;j<10;j++){
        if(tenw[j].frequency)fprintf(fout,"%s: %d\n",tenw[j].content,tenw[j].frequency);
    }
}

void topteng(void){
    wordgroup teng[10]={0};
    long int j,m,n;
    for(j=0;j<GROUPNUM;j++){
        if(Hashg[j].frequency ){
            for(m=9;m>=0&&(!teng[m].frequency||Hashg[j].frequency>teng[m].frequency);m--);
            if(m+1<=9){
                for(n=9;n>m+1;n--){
                    teng[n]=teng[n-1];
                }
                teng[m+1]=Hashg[j];
            }
        }
    }
    for(j=0;j<10;j++){
        if(teng[j].frequency)fprintf(fout,"%s %s: %d\n",teng[j].firstword->content,teng[j].secword->content,teng[j].frequency);
    }
}

性能分析

 

 

            

 

release模式下运行时间大概16s左右,可以看到:

  1. fgetc耗时量巨大,但是读字符是必不可少的操作所以不好修改。
  2. 对每一个字符判断处理的函数wordjudge调用次数也很多,但是要处理多少字符就要调用,多少次函数,所以也没有太多优化空间。
  3. 对词组和词的存入判断占用也较大,但是也是情理之中。
  4. 遍历词组和词哈希表找前十倒是没有花多少时间。
  5. 为了提高时间效率,开了较大的空间,属于时间和空间的互换。

PSP表格

测试结果

VS下(左助教右我的)

 

Linux下(左我的右助教)

可以看到,在两种测试环境下,前十单词和词组都和助教都是一样的。但是两种环境下的单词字符和行数都有数百的误差,实在不是很清楚原因。

 

项目经历及总结分析

  1. 一开始我的畏难情绪十分严重,因为自己实在比较菜,除了c和dev啥也没用过,所以这星期的任务实在太多。所以我选择先写完其它作业再全力肝软工。感觉这个决定很正确。因为我在构思的时候也确实收到了一些写了博客的同学的启发。
  2. 本次作业,周一周二写完基本函数模块,周三一轮测试,发现不行并进行了大的重构。周四中午完成VS上的调试,周四晚10点完成Linux下的移植。
  3. 一开始词和词组的哈希表是26*26*26*26的数组,按照前4个字母排序。由于这种方式和题目要求契合的较好,我一度觉得这是很好的解决方案。但是运行到大文件的时候还是出现了读取非常慢的情况。几经考虑后决定加大数组容量,并优化词组的存储方式和哈希函数,使程序明显清晰了不少,速度和稳定性都有提升。
  4. 这次个人作业让我了解了VS的一些基本操作,以及使用GitHub,遍历文件夹,以及Linux的一些简单命令和操作,还有关于程序移植方面的知识,以及练习了一些调试的技能,可以说是逼着狠狠恶补了一把。
  5. 这次软工还我明白了时间分配的重要性,从周一一直肝到周五,欠下了一堆的作业,上课的状态也不是很好,之后又要花好多时间来补,而且马上期中考又要加实验课,实在伤不起。以后要么更新技能,提高水平,要么“有舍才有得”/无奈。