代码改变世界

努力做个Pragmatic Programmer

2006-03-31 15:35 FantasySoft 阅读(...) 评论(...) 编辑 收藏

        在公司里,由于整个开发流程相对规范,整天都是拿着文档做开发,动脑筋也不太多了,久而久之难免会感到厌烦,还好有一些不需要循规蹈矩的小task可以调和一下。事实上,对于这些小task,Leader通常都会很头痛,因为都是一些如果手工完成都会让人抓狂的,繁琐而无聊的工作。每每接到这些看似很无聊的task,我都会考虑用较为聪明的方式将其完成。虽然俺的劳动力低廉,但是不想被这些无聊的工作坏了心情,也正好可以动动自己那快要生锈的脑筋。
        这次来的task有点棘手:在3万多行的文本中找出符合要求的文本段。这些文本段当中,可能有一部分是相同的,所以还要统计相同文本段出现的次数,最后以一个统一的格式输出到一个文件中。既然是文本的处理,最自然就是想到了处理文本的利器:Python。好,明确了task,也选好了工具,开工!
        首先我大致看了一下整个文本的组成,发现文本无非是由两种类似的文本段组成的:包含字符串"Servelet.Engine.Transports"的文本以及包含"Thread"的文本。而task的要求则是从中挖出包含"Servelet.Engine.Transports"的文本段,并且在此基础上统计相同文本段出现的次数。既然如此,就不要一步到位了,来个步步为营吧——先将包含"Thread"的文本段给过滤掉,得到只包含"Servelet.Engine.Transports"的文本,最后再做统计。那么,问题就细化为如何过滤文本了。于是,我翻阅了一下文档之后,写出了以下的代码:

import re
log 
= open("20060323.txt", "r"
)
output 
= open("servlet.txt", "w"
)
num 
=
0
for line in
log.readlines():
    
if re.search("Servlet.Engine.Transports"
, line):
        num 
= 1

        output.write(line)
    
elif line == "\n" and num == 1 :
        output.write(line)
        num 
=
0
         
log.close()
output.close()


真的很简短喔!嘿嘿,注意啦!我可不是在自卖自夸哦,偶是在夸着Python提供了很不错的API,让我可以用很简单的方式完成了所需的工作。对于以上代码,需要注意的地方是re这个module以及module中search函数,还有关于文件的简单操作。由于open函数存在于__builtins__中,因此不需要import任何的module就可以直接使用了,而open函数返回的正是一个file类型的对象。对于open函数来说,第一个参数是文件名,而第二个参数则是操作文件的方式,"r"代表读操作,而"w"则代表写操作。如果没有指明具体的文件操作方式,那么默认就是读操作了。在获得文件对象log之后,通过readlines方法就可以获得整个文件所有行的文本内容,并且存放于一个list中,便于进行遍历。进入到for...in循环体,我们可以见到一个特别的module——re。re是一个用于正则表达式的module,search函数的第一个函数表示正则表达式的pattern,而第二个参数则是用于校验匹配的字符串。一旦该字符串符合第一个参数给出的pattern,那么将返回一个MatchObject对象,否则就返回None,所以这样一个方法就能够帮助我找出符合条件的字符串了。不过,再仔细想一下,我只不过是要找出包含某个固定子串的字符串而已,出动正则表达式真的有杀鸡用牛刀的感觉了。于是,我将if后面的条件判断换成了string.find(line, "Servlet.Engine.Transports") > 0,并且引入string这个module。这下子总算心里舒坦了,一个萝卜一个坑,简单的string操作还是用string module比较爽。当然了,速度上也有了提高。好,第一步大功告成,接下来就是对相同文本段进行统计了。
        既然是统计,第一步就要知道在所有的文本段中有多少个是完全不同的,这样先得到一个不包含重复元素的列表。然后再根据这个列表重新遍历所有的文本段,得到统计结果。这样一分析,问题又细化为如何得到一个不包含重复元素得列表中了(需要说明的一点,对于第一步输出的文件,我以List嵌套的方式表示之:[[txtline1,txtline2, txtline3], [txtline5, txtline6], ...])。我一开始想到了set这个数据类型,因为它就是用于表示一个不包含重复元素的数据列表,同时也提供了将list转化为set的方法。例如(以下引自Python Tutorial):

>>> basket = ['apple''orange', 'apple''pear', 'orange', 'banana']
>>> fruits = set(basket)            #  create a set without duplicates

>>> fruits
set([
'orange', 'pear', 'apple', 'banana'])


于是,我将set(iterable)这个函数应用到自己构造的List中,却遇到了这样的错误:TypeError: list objects are unhashable。原来set(iterable)这个函数并不能应用在List的元素也是List的情况下。就这样,第一个想法被否定了,偶那生锈的脑袋也短路了,一时间无从下手。后来我想,既然是对List的操作,还是从List入手吧,于是我开始翻阅有关List的函数。很快的,我发现了List有一个count函数,用来统计List中某个元素出现的次数的。这让我欣喜若狂,因为solution已经找到了,原来就是这么简单:

tmpList  = []
for sortedItemList  in sortedList:     # sortedList就是包含了所有文本段的List

 
     if tmpList.count(sortedItemList) == 0:
        tmpList.append(sortedItemList)


最后得到的tmpList就是一个不包含重复文本段的List了。有了这样一个所有元素都是唯一的List,最后的统计也很好办了。这不是还有刚刚帮了大忙的count函数吗?

unique = open("D:\\uniqueList.txt""w")
for tmpItemList in
tmpList:
    
for tmpItem in 
tmpItemList:
          unique.write(tmpItem)
     countno 
= sortedList.count(tmpItemList)


好了,大功告成!一个繁琐的task在Python的帮助下,变得简单而有趣,而我也不由得被注重实效的Python再次深深打动了。可是,每一次都被打动,而每一次都因为这样那样的借口把她晾在了一边,直到有了棘手的task才想起她,真的很糟糕。要做一名pragmatic programmer,光说不练可不行啊!OK,思过完毕,我们下次再接着聊Python,哈哈!