个人进度(四) 第一次个人作业总结 第一次个人作业总结

需求分析

  对源文件(*.txt,*.cpp,*.h,*.cs,*.html,*.js,*.java,*.py,*.php等,文件夹内的所有文件)统计字符数、单词数、行数、词频,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。

  具体要求:

  1. 统计文件的字符数(只需要统计Ascii码,汉字不用考虑)

  2. 统计文件的单词总数

  3. 统计文件的总行数(任何字符构成的行,都需要统计)

  4. 统计文件中各单词的出现次数,输出频率最高的10个。

  5. 对给定文件夹及其递归子文件夹下的所有文件进行统计

  6. 统计两个单词(词组)在一起的频率,输出频率最高的前10个。

  ps:

a) 空格,水平制表符,换行符,均算字符

  b) 单词的定义:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。

  英文字母:A-Z,a-z

  字母数字符号:A-Z,a-z,0-9

  分割符:空格,非字母数字符号

 

时间安排

  PSP表:

流程

预估时间/h

实际时间/h

需求分析

0.5

0.5

具体设计

1.5

1

具体编码

6

8

代码测试

2

2

性能优化

2

1

总结报告

2

2.5

  

  具体时间线:

   3.27:    10:00am-10:30am:看博客明确题目要求;

       10:30am-11:30am:确定代码模块与功能,查阅资料确定使用的库;

          2:00pm-4:00am:完成文件夹遍历,读入所有路径名;

          4:00pm-5:00pm:建立好哈希表;

          7:00pm-10:00pm:完成字符统计、行数统计、单词统计(修改了哈希表映射结构);

    3.28:    2:00pm-3:00pm:完成输出最频繁的10个单词功能;

                        3:00pm-4:30pm:完成输出最频繁的10个词组功能;

           4:30pm-5:00pm:调整变量类型(char* -> string)以适应大测试集;

    3.29:    7:00pm-9:00pm:用空文件、官方提供文件等测试集进行测试,调整程序;

           9:00pm-10:00pm:性能分析与代码优化。

 

实现过程

1、首先确定遍历文件方式,为减少模块间的耦合度,先提取出总目录下所有非目录文件的文件名,用 string[] fileName 来存储,方便之后的文件操作。c标准库里提供了一结构体  _finddata_t  来记录文件信息,其中就包含了该文件是否为目录文件,若是,则递归调用遍历函数访问子文件夹;若否,则用  _findnext()  函数找下一文件。将访问过的文件的文件名与其目录结合成路径保存在  fileName  中。

void SearchFiles(char* dir,string fileName[],int& fileNum)
{
    intptr_t handle;
    _finddata_t findData;
    char searchdir[200],newdir[200],newfile[200];

    strcpy_s(searchdir, dir);
    strcat_s(searchdir, "\*");
    handle = _findfirst(searchdir, &findData); 

    if (handle == -1)
    {
        cout << "Failed to find first file!
";
        return;
    }

    do
    {
        if (strcmp(findData.name, ".") != 0 && strcmp(findData.name, "..") != 0)
        {
            if (findData.attrib == _A_SUBDIR)
            {
                strcpy_s(newdir, dir);
                strcat_s(newdir, "\");
                strcat_s(newdir, findData.name);
                SearchFiles(newdir,fileName,fileNum);
            }    
            else
            {
                strcpy_s(newfile, dir);
                strcat_s(newfile, "\");
                strcat_s(newfile, findData.name);
                fileName[fileNum]=newfile;
                fileNum+=1;                
            }
        }
    } while (_findnext(handle, &findData) == 0);    

    _findclose(handle);   
}

2、单词与其出现个数的关系用哈希表来存储,这里采用 C++ STL 函数  unordered_map() <string,int> wordValueMap ,由于按照要求 file 与 FilE231为同一单词,则再建立一张哈希表  unordered_map() <string,int> wordValueMap ,其中 wordValueMap  的  string  存储一个单词最简单的名字,即脱去所有后缀数字且字母全部转化为小写,而  wordNameMap  则将单词的最简名字映射到实际出现的且按照要求应该保留的名字。这种方式将查找名字与选取合适的名字分开,可以更方便地进行哈希表查找。

3、对  fileName  中每个文件进行读操作,使用  fgetc()  进行逐个字符阅读,并存储在  string word  中,若遇到非字母数字字符,则判断  word  是否以四个字母打头,若是则  word 是一个单词,将其写入  wordValueMap  (无则添加,已存在则个数加1),并判断是否要在  wordNameMap  中更改它的实际名字。在此过程中同时记录字符数、单词数、行数(出现“ ”则加1,且每个文件最末尾加1)。为了能够统计词组情况,设置了一全局数组  string allWords[] ,将读文件过程中所有的单词(最简单形式)读入  allWords 。

  

void ReadFile(FILE *fp, unordered_map<string, int>& wordValueMap, unordered_map<string, string>& wordNameMap, int& chrtCount, int& wordCount, int& lineCount)
{
    char ch;
    string word;
    unsigned int i,j;
    unordered_map<string, int>::iterator itValue;
    unordered_map<string, string>::iterator itName;

    while ((ch = fgetc(fp)) != EOF)
    {
        if (ch >= 32 && ch <= 126)
            chrtCount++;
            
        if ((ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122))
            word=word+ch;

        else
        {
            if (word.length() >= 4)
            {
                for (i = 0; i < 4; i++)
                    if (!(((word[i] >= 65) && (word[i] <= 90)) || ((word[i] >= 97) && (word[i] <= 122))))
                        break;
                if (i >= 4)
                {
                    for ( j = word.length() - 1; word[j] >= 48 && word[j] <= 57; j--);
                    string newWord(word, 0, j + 1);
                    for (j = 0; j < newWord.length(); j++)
                        if (newWord[j] >= 65 && newWord[j] <= 90)
                            newWord[j] = newWord[j] + 32;

                    allWords[wordCount] = newWord;
                    wordCount++;

                    itValue = wordValueMap.find(newWord);
                    if (itValue == wordValueMap.end())
                        wordValueMap.insert(pair<string, int>(newWord, 1));
                    
                    else
                        itValue->second++;

                    itName = wordNameMap.find(newWord);
                    if (itName == wordNameMap.end())
                        wordNameMap.insert(pair<string, string>(newWord, word));

                    else if (word.compare(itName->second) < 0)
                        itName->second = word;
                }
            }
                
            word.erase();
            if(ch == '
')
                lineCount++;
        }
    }

    lineCount++;    
}

4、输出文件时可直接输出字符数、行数、单词数,遍历  wordValueMap  找到个数最大的10个单词,用数组  string topTenWordName[]  和  int topTenWordNum[] 来保存名字和个数,在遍历过程中通过与  topTenWordNum 元素不断比较更新该数组,最终得到10个最频繁单词,其真实名称可以通过查询  wordNameMap  获得。将这10个单词与其个数输出。

int GetTopTenWords(unordered_map<string, int> wordValueMap, unordered_map<string, string> wordNameMap, string topTenWordName[], int topTenWordNum[])
{
    unordered_map<string, int>::iterator itValue = wordValueMap.begin();
    unordered_map<string, string>::iterator itName;
    int i, j,wordNum;
    for (i = 0; i < 10; i++)
    {
        topTenWordNum[i] = 0;
        topTenWordName[i] = "