第一次个人作业总结
需求分析
对源文件(*.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] = "