Github地址:https://github.com/littlenorthwest/personal-project
PSP Table
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 80 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 80 |
Development | 开发 | 1180 | 1452 |
· Analysis | · 需求分析 (包括学习新技术) | 400 | 450 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 | 50 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
· Design | · 具体设计 | 300 | 380 |
· Coding | · 具体编码 | 200 | 300 |
· Code Review | · 代码复审 | 60 | 80 |
· Test | · 测试(自我测试,修改代码,提交修改) | 50 | 60 |
Reporting | 报告 | 420 | 550 |
· Test Repor | · 测试报告 | 120 | 200 |
· Size Measurement | · 计算工作量 | 200 | 230 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 100 | 120 |
合计 | 1660 | 2082 |
Preliminary Idea
首先来回顾一下本次实践任务的题目:
-
统计文件的字符数:
- 只需要统计Ascii码,汉字不需考虑
- 空格,水平制表符,换行符,均算字符
-
统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。统计文件的有效行数:任何包含非空白字符的行,都需要统计。
- 英文字母: A-Z,a-z
- 字母数字符号:A-Z, a-z,0-9
- 分割符:空格,非字母数字符号
- 例:file123是一个单词, 123file不是一个单词。file,File和FILE是同一个单词
-
统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
-
按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000
从以上的题目中,可以大体把大题目划TO分为四个小题目来逐一进行攻破。1.统计字符数。2.统计行数。3.统计单词数。4.输出词频Top10的单词。经过分析,可以发现3.与4.是紧密联系在一起的。只有先完成了单词数的统计(分割),才能比较方便的去进行词频的比较。
这四个方面的要求,第一与第二个要求是比较方便完成的,我打算直接通过“fstream”这个库里自带的函数来完成这两项要求,当然还要进行稍微的修改才能完全符合题目的要求。而对于后面的词频问题,我觉得可以使用map来进行,由于我之前不怎么熟悉map的使用,为此,我还是花了一定的时间去补学这一方面的知识。
有了一个大概的思路之后,再读了一遍题目之后,发现题目需要我们满足使用者可以通过cmd命令窗口进行把参数传入main函数里,这一点我一开始还是感觉无从下手,不知道如何完成的,后来通过查询了一定的资料后,还是满足了这一方面的需求。有了一定的思路框架之后,便可以开始撰写代码了,由于是要处理文档内容,所以我们可以使用到fstream库来进行文件打开、关闭以及文件内容的读取。
由于这部分只是我的一个大概思路,所以并没有具体的解释该如何实现这些功能,功能实现的解释,我将会在Implementation Process 这一部分进行解释。
Implementation Process & Code Description
- 统计字符数。
int CountChar(char * filename) { int cnt = 0; char c; std::fstream file; file.open(filename, std::ios::in); while (file.get(c))
{ cnt++; } file.close(); return cnt; }打开文件, 通过使用get(c)函数来判断当前所取得信息是否是字符,如果是字符则记录在cnt变量里。
-
统计行数。
int CountLines(char *filename) { ifstream ReadFile; int n=0; char line[512]; string temp; ReadFile.open(filename,ios::in);//ios::in 表示以只读的方式读取文件 if(ReadFile.fail())//文件打开失败:返回0 { return 0; } else//文件存在 { while(getline(ReadFile,temp)) { n++; } return n; } ReadFile.close(); }
同样,可以使用到“fstream”库里所带的函数来解决行数的统计问题,通过getline()函数,可以比较方便地统计出行数。部分代码解释已附在代码后面。
- 统计单词数。
int CountWords(string filename) { const char *t; int flag = 0; int cnt = 0; string temp; unordered_map<string, int> newmap; //使用unordered_map更为高效,其底层为hash表,而map是红黑树,效率而言自然是前者高啦~ ifstream File(filename.c_str()); //字符读取文件 while (File >> temp) { flag = 0; transform(temp.begin(), temp.end(), temp.begin(), ::tolower); //首先,将文件内容转为小写,从而满足题目要求。也方便了输出。 t = temp.c_str(); if (temp.length() >= 4) { for (int i = 0; i < 4; i++) { if ( t[i] >= 97 && t[i] <= 122) flag = flag+ 1; } if (flag == 4) //即是满足成为单词的条件!sign=4,即表示有前四个字符为字母。 { if (newmap[temp] == 0) cnt++; newmap[temp]++; //通过Key值(此处为string)来查阅“hash“表,将其value(即累计字母数目)+1; } } } return cnt; }
说明:由于后来发现助教说“需要注意输出格式”,然后发现自己并没有使用正确的格式,所以这部分本来我是把它和词频放在一起的。考虑到时间可能会有点来不及,因此就直接单独把词频里统计单词数的函数稍微进行修改后独立出来,因此可能会有部分重叠。代码解释:首先读取文件,后将字符串均小写化,既方便了对单词与否的判断,又方便了最终输出是以小写化的形式进行。然后,定义一个flag变量,用来统计前四个字符是否是字母,从而判断是否是单词(字符串长度小于4的可以预先排除)。这里可以直接把判断好的单词放入unordered_map这个哈希表里,以其string作为它独一无二的id,然后记录它出现的次数(即词频),然后,我们会遇到同一个单词遇到多次的情形,但并不可以直接就记为一个新单词,因此我们依据“单词在哈希表里的value值是否为0”来判断其是否是新单词,若为0,则将cnt加一,最后输出cnt的值,
- Top10与词频。
void WordFrequency(string filename) { ofstream fout; const char *t; int flag = 0; int cnt = 0; string temp; unordered_map<string, int> newmap; //使用unordered_map更为高效,其底层为hash表,而map是红黑树,效率而言自然是前者高啦~ ifstream File(filename.c_str()); //字符读取文件 while (File >> temp) { flag = 0; transform(temp.begin(), temp.end(), temp.begin(), ::tolower); //首先,将文件内容转为小写,从而满足题目要求。也方便了输出。 t = temp.c_str(); if (temp.length() >= 4) { for (int i = 0; i < 4; i++) { if (t[i] >= 97 && t[i] <= 122) flag = flag + 1; } if (flag == 4) //即是满足成为单词的条件!sign=4,即表示有前四个字符为字母。 { if (newmap[temp] == 0) { cnt++; } newmap[temp]++; //通过Key值(此处为string)来查阅“hash“表,将其value(即累计字母数目)+1; } } } //cout << "words:" << mm << endl; vector<pair<string, int> > newvector; Sort(newmap, newvector); //进行排序。(关键) //输出文件result.txt // cout << "Top10:" << endl; fout.open("result.txt", std::ios::out | std::ios::app); for (int i = 0; i < newvector.size() && i < 10; i++) //输出词频Top10的单词,同时考虑了没有10个单词的情况。 fout <<"<"<< newvector[i].first <<">"<< ": " << newvector[i].second << endl; //cout << tVector[i].first << ": " << tVector[i].second << endl; fout.close(); }
代码解释:首先当然就是先将内容小写化啦~接下来的统计之前想到用map来做,可是后来发现还有一个神奇的unordered_map,前者底层实现结构为红黑树,后者为哈希表,从性能上来考虑,最终还是选择使用后者。首先,把所读取的string(即字符串)作为其key值(每个单词各不相同),通过每个单词的key值(string值)就可以检索到单词的位置,value值则是它的词频了。通过每次取出一个字符串,每次分别判断其是否是单词,若是,则将其信息输入到unordered_map里,当所有的字符串都分析判断完毕之后,那么接下来的工作就是要对单词以其词频为依据进行排序。本来打算手写排序算法,但是上网搜查一番之后发现有个十分好用的vector容器,它可以搭配sort算法和一个compare函数(可根据需求进行撰写)来实现降序或升序的排列。还算是比较友好的工具(哈哈!学到了!!)~然后把之前我们得出的“哈希表”(unorder_map)作为参数之一输入,便可以得到一个排好序的单词词频表。然后,根据题目需求,再把top10输出到“result.txt”文件里即可。具体代码如上所示。
Analysis
性能分析截图如下所示:(统计进行10000次的报告)
通过以上截图,可以看到,此次WordCount项目,CPU占用率最高的是WordFrequency统计top10词频这一方面。这也符合我实验之前的预期。通过不停的判断单词,累计计算得到单词词频,并把词频记录在哈希表的value值里。然后通过sort()进行排序而后输出,sort()函数是比较费时的。
Experience