首页 > 代码库 > 个人项目-词频统计
个人项目-词频统计
开发语言:
C#
开发平台:
Visual Studio 2013 Professional
预计时间:
建立工程基本框架:半小时
模块-递归寻找所有文件:半小时
模块-扫描&分离单词:一个半小时
Debug&优化:两小时
实际时间:
预计时间x3
事实证明,预计时间是建立在一个相当顺利的基础上才能达到的。在实际Coding中,由于对C#文件等操作不熟悉,以及扩展模式时更改框架,还有思路混乱等等,花掉了不少时间,这些时间应该要加入预计当中的。
主要遇到的问题:
1.字符串比较问题
C#默认并不依照ASCII码字典序比较字符串,而是与语言&文化相关,也就是在Windows资源管理器里对文件名排序时用的比较方法,a是排在A前面的。解决这个问题有很多方法,以下是其中一种:
比较字符串:string.CompareOrdinal(s1, s2)
忽略大小写判断字符串是否相等:string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)
这个Ordinal我真的不知怎么翻译。
2.控制台与文件流的统一接口
我在设计output方法时,引入参数StreamWriter,这样就可以选择在debug的时候将输出定位到Console了。但是翻遍了Console的成员,尝试好多都失败了。不过最后还是找到了办法,将参数类型改为TextWriter,由于Console.Out就是这个类的,而StreamWriter是TextWriter的继承类,所以统一了两者的接口。至于两个Writer有什么区别,暂时没有深究。
3.分离单词
一开始想用正则表达式的,折腾好久总算写了个感觉正确的,但还是有问题:题目要求单词前后都是分隔符,但是正则表达式查找时分隔符之间不能覆盖。比如说“hello world”只能识别hello,而“hello world”就能识别hello和world。
没办法,还是老老实实逐字符处理吧。
4.扩展模式实现
我的框架是按照标准模式设计的。标准模式实现之后,加入扩展模式,必然要更改一些模块。主要更改如下:
* 命令行参数判断,不用多说
* 将“单词表”直接作为”单词组表”,排序方法不变,之所以这么做,是因为扩展模式在语义上本来就不是标准功能的一部分,既然如此,List不标准让人误会又有何妨?况且,这是成本最低的做法。
* 独立一个CurrentWord方法,用以寻找当前位置是否存在一个单词,如果有,则返回。作业要求e2或e3模式下单词分离符只能是一个单独的空格,所以必须手动判断两个或三个单词,将CurrentWord模块化是必须的。
* 输出相应处理
5.CPU采样
一开始用简单文件测试,选择Sampling模式时,弹出一个窗口
stackoverflow上有人给出的解释是,由于进程一下子就运行完了,VS还没来得及采集数据,所以出现了报错。
解决办法是更改为Instrumentation模式,或者让程序变慢些(-_-#)。
测试数据:
# | 测试目的 | 描述 | 输出 | 备注 |
1 | 未指定扫描文件夹 | 控制台:Please specify a directory! | ||
2 | 参数错误:-e4 | 控制台:The argument must be -e2 or -e3. Scanning cancelled. | ||
3 | 文件夹不存在 | 控制台:The directory specified doesn‘t exist! | ||
4 | 文件夹为空 | 空文件 | ||
5 | 验证单词判定&分离 | 一个txt文件,内容: hello #kitty 3english中too xxx12 xx second hello aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq | hello: 2 aaa: 1 bbb: 1 ccc: 1 ddd: 1 eee: 1 fff: 1 ggg: 1 hhh: 1 iii: 1 jjj: 1 kitty: 1 kkk: 1 lll: 1 mmm: 1 nnn: 1 ooo: 1 ppp: 1 qqq: 1 second: 1 too: 1 xxx12: 1 | 输出了所有单词 |
6 | 验证统计&排序 | 一个txt文件,内容: hello Hello heLLo yyy XXX xxx xxx | Hello: 3 XXX: 3 yyy: 1 | |
7 | 验证文件类型 | 若干个文件,内容都为: hello Hello heLLo yyy XXX xxx xxx 文件类型分别为: txt, cpp, h, cs, png, (null) | Hello: 12 XXX: 12 yyy: 4 | |
8 | 验证递归寻找文件 | 根目录下是一个文件和一个目录,目录下是一个文件和一个目录,目录下又是一个文件。 三个文件都是txt,内容一致: hello Hello heLLo yyy XXX xxx xxx | Hello: 9 XXX: 9 yyy: 3 | |
9 | 验证扩展模式-e2 | 单个txt文件,命令行参数-e2 if you do not learn to think when you are young you may never learn Edison zzz zxz you You yOu you you #like that | You yOu: 4 are young: 1 learn Edison: 1 may never: 1 never learn: 1 not learn: 1 think when: 1 when you: 1 you are: 1 you may: 1 | 1.只列出前10个 2.按单词频度顺序 3.视为相同的单词组,选择字典序最先者:You yOu 4.连续单词计数:4个you you 5.分隔符只能是单个空格(you like和like that不在列,它们本应该排在you may的前面 |
10 | 验证扩展模式-e3 | 单个txt文件,命令行参数-e3 if you do not learn to think when you are young you may never learn Edison zzz zxz you You yOu you you #like that | You yOu you: 3 are young you: 1 may never learn: 1 never learn Edison: 1 think when you: 1 when you are: 1 you are young: 1 you may never: 1 young you may: 1 zxz you You: 1 | 只列出前10个(zzz zxz you未出现) |
优化:
程序扫描得相当慢,出乎我的意料。我用The Kite Runner原版小说(500+k)扫描,预计要40分钟左右。
我提取了前面的325行,CPU Sampling测试结果:
总时间15秒。
看起来是一个Count方法占用了大多数的时间,定位到代码:
原来是每次判断字符串长度时,都调用方法计算!
我将所有Count()方法改成了Length属性,再分析:
时间缩短至1.5秒左右,是原先的十分之一!
我再用The Kite Runner测试,几秒钟就出来了。速度快了不止一星半点。
优化性能往往意味着增加代码复杂度,有的地方我并没有深入优化。下面给出优化和未优化的部分:
已优化的部分:
* 利用查询表达式“推迟执行”的特性,一边遍历文件一边统计该文件的单词数。(时间)
* 如上述将Count方法改为Length属性。(时间)
* 构建单词时,使用StringBuilder类。(时间)
未实现的优化:
* 直接对流文件处理,而不是先把内容读到一个string里。(空间&时间)——个人感觉流的指针移动操作比较复杂,况且时间提升不会太大,而空间呢,文本文件一般也不会太大。
* 用正则表达式替代手工判断。(时间)——也许效率能提升不少吧
* 并行计算。(时间)——不会
* 还有很多我没想到的……
个人项目-词频统计