个人项目很快结束了。一个很简单的程序,有所收获,但是还有很多自己不满意的地方。汇总如下。
写一个命令行程序,实现对某个目录下的所有指定ASCII文件进行词频统计。程序的命令行参数为目录地址和文件名格式,输出为频率最高的前100个单词。单词分隔符为非英文字母,大小写视为同一字母,长度不少于2个字母才视为单词。单个文件不超过100M,所有文件不超过10G。
使用_findfirst等函数遍历文件夹。对于每一个文件,先全部放入数组,再遍历该数组,遇到非字母字符则将该单词放入STL的map<string, int>中统计词频。最后使用STL的vector的partial_sort
进行排序。
任务开始前对工作量有过估计,与实际花费对比如下表,总体还是比较靠谱的。
Work item |
size (estimation/real) (lines) |
time (estimation/real) |
Learn to read all of the files match the pattern in a directory by C++ |
20/70 |
2h/3h |
Design the data structure |
80/20 |
2h/1h |
Code the runnable program |
100/140 |
4h/6h |
Unit testing and debugging |
|
4h/- |
Total |
200/230 |
10h |
对于读文件夹,发现对于“*.*”,_findfirst会返回一个空文件,因而对于参数是否为“*.*”做了判断,使得代码变长。
对于数据结构,之前计划自己来写,但是发现map足以应对,所以省去了这一块。
当然这次的作业思路比较简单,尽管个人没有什么实践经历,但对项目的进度掌握还算满意。希望接下来的大项目中可以继续做好这一点。
1、 编程语言选择。听同学讲C#有现成的遍历目录和Hash数据结构,但之前从来没有接触过C#,为了保证实现,最后选择了C++。接下来恐怕不得不补习C#了。
2、 读文件。由于IO耗费时间最明显,所以对于每个文件直接将它们全部读入内存。当然这是因为测试中单个文件不会超过100M。为避免bug,读文件前先进行了文件大小判断(这里将100000000定义为宏,但是没有注释,有硬编码的嫌疑)。应用。读文件用来C的函数而不是C++的fstream,速度有明显提高。
3、 分词。首先,对于每个字母,开始是用一个字符型函数处理,若是字母,则返回其小写,否则返回NULL。感谢鹏飞师兄提醒,在这里的多次比较大小花费时间较多,更好的做法是直接建立长为256的字符型数组来代替该函数。因为直接访问地址要快得多。然后,存储单词用了STL的string,每次读入新字母则要追加在string后面,可能需要重新申请内存,再复制过来,造成时间浪费。可以考虑自己实现一个动态数组类,每次申请足够的空间并设定其动态长度。另外,作业中没有要求处理行末有连字符“-”的情况,可能使统计有差错(这个比较复杂,有时候行末的连字符不一定表示前后时一个词)。
4、 统计和排序。统计词频用了map,是STL中用红黑树实现的,比Hash要慢。如果自己实现Hash会有明显提高。对于排序,曾经考虑过根据查到的权威语料统计结果,对常用的N个词(N应该大于100,取500?)先建好堆,再对所有单词进行调整,这样之前有较好的顺序,堆调整的次数要少一些,但是没有实现,不知道具体的效果(这很依赖于测试数据的类型,小说和学术文章的词频肯定有很大不同)。而且,这里使用的partial_sort是堆排序,不是稳定的排序,不能达到相同频率的词按字典序输出的要求。最后,对于极端的情况,例如所有10G的文件中的单词频率都很低,而单词的种类数巨大,就不得不存放在硬盘中,这样再进行排序就会有更多的问题。这些我都没有考虑。
5、 注释。把自我约束放到最低,还好我的代码有正常的缩进。但是几乎没有注释对别人来说真的不能接受。接下来的结对和团队项目中我要做好这一点。技术不过硬的话,至少也要守住程序员最基本的节操。
6、 性能分析和单元测试。完全没有学这个,该补补了。