首页 > 代码库 > 个人项目-词频统计

个人项目-词频统计

开发语言:

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里。(空间&时间)——个人感觉流的指针移动操作比较复杂,况且时间提升不会太大,而空间呢,文本文件一般也不会太大。

* 用正则表达式替代手工判断。(时间)——也许效率能提升不少

* 并行计算。(时间)——不会

* 还有很多我没想到的……

个人项目-词频统计