首页 > 技术文章 > 20180918-1 词频统计

orion1994 2018-09-24 21:04 原文

作业要求参见 https://edu.cnblogs.com/campus/nenu/2018fall/homework/2126

一、项目功能的重点、难点 及执行效果

1.功能1 小文件输入。 为表明程序能跑,结果真实而不是迫害老五,请他亲自键盘在控制台下输入命令。

(1)读文件操作

  利用open()方法读文件,with可以自动关闭文件

def read_words(filename):
    with open(filename, encoding='utf-8') as f_obj:
        contents = f_obj.read()
        return contents

(2)单词划分

  单词划分应该算是这个程序中的重点以及核心部分,不同的划分依据会得到不同的统计结果。根据空格和破折号来划分单词,并对逗号,双引号,回车,空格,句号进行替换,对单词进行整理。因为英文文本中有的单词后面会紧跟着一个符号,如果仅仅依靠空格和破折号来划分单词,会得到带有标点符号的单词。之后用lower()将字符串变为小写,方便统计,split()来将单词划分开来。

for ch in '\r.,"':
        str = str.replace(ch," ")
    strl_ist = str.lower().split()

(3)控制台输入命令行参数

  作业四个功能均需要支持命令行参数输入,通过argv来对python程序传递参数,argv[0]是程序本身的名字,通过对argv[1],也就是第一个参数来判断,进入不同的处理方法(对应不同的功能),-h是帮助命令,提示用户输入格式,当argv[1]=='-s'时将argv[2]也就是需要读入的文本名传递给inputfile_1中,而对len(sys.argv)参数个数的判断是应对其他功能的,我会在后面说明。

def main(argv):
    if sys.argv[1] == '-h':
        #print(sys.argv[1])
        print ('wf -s file.txt')
        print ('wf file')
        print ('wf dir')
        print ('wf -s < file.txt')
        sys.exit()
    elif sys.argv[1]=="-s":
        if(len(sys.argv)==3):
            inputfile_1.append(sys.argv[2])
            start(inputfile_1)
if __name__ == "__main__":
    main(sys.argv[1:])

(4)单词统计

  对划分好的单词进行统计,新建一个count_dict字典,遍历所有的单词,如果不在count_dict中,则count_dict中单词对应的value加1,否则就记为1,这样就将重复的单词个数进行了统计,count_dict也就存储了{['单词'],个数},方便我们输出结果。

def wordcount(strl_ist):
    count_dict = {}
    for str in strl_ist:
        if str != '':
            if str in count_dict.keys():
                count_dict[str] = count_dict[str] + 1
            else:
                count_dict[str] = 1
    count_list=sorted(count_dict.items(),key=lambda x:x[1],reverse=True)
    return count_list

(5)生成exe文件

  利用pyinstaller工具对.py文件进行打包处理,生成可执行文件.exe

  通常命令是pyinstaller -F demo.py

 

  红线处显示已经打包成功

  dist目录内有已经生成的wf.exe可执行程序

(6)功能1的效果截图

 2.功能2 支持命令行输入英文作品的文件名,请老五亲自录入。

(1)单词划分的新方法

  在处理小文件时候,按照前文提到的划分方法是可行的。但在大量数据的文本中,会有很多想不到的情况出现,使用开始的方法对war_and_peace进行划分。

  可以看到和老师git上测试用例的结果一致,但是我们将字典中所有的数据输出出来看看。

  看红色矩形框中的单词,我们不难看出,之前的划分方法,虽然将单词划分开来,但对于符号没有处理干净,因为有些单词在句尾接的不一定都是句号。这些不能认为是单词,所以我重新写一个字符串处理的函数用来处理这些异常状况。

  先用word输入数个单词,并插入符号。

  看红色矩形框处,我们可以看到像http://www.gutenberg.org的网址出现在word中是一个单词,在这里那就把网址定义为一个单词,这几行也出现了很多异常情况,一共24个单词,下面我们用写的处理函数处理一下。

  我们可以看到出现在单词左右的符号被正确的剔除掉了,输出的单词总数也与word统计的总数一样,现在的方法明显要好于之前提到的方法。算法如下。

def dispose_words(string):
    string = string.replace('\n',' ').replace(',',' ')
    s1 = list(string)
    num = len(s1)
    s1.append(' ')
    for i in range(num):
        if s1[i] in '."?\')-(;#$%&*!':
            if str(s1[i-1].isalnum())=='True' and (str(s1[i+1].isalnum())== 'True'):
                pass
            else:
                s1[i]=' '
    for i in range(num):
        if s1[i] in ':':
            if s1[i+1]=='/':
                pass
            else:
                s1[i]=' '
    s = ''.join(s1)
    #print(s)
    return s

  这个函数主要是遍历每一个字符当出现标点符号时,会判断上一个和下一个字符是不是同时为字母或者数字,如果这样的话,就什么都不操作,否则就将符号换成空格来划分单词。之所以先把逗点直接替换成空格,是因为或许会出现格式不准,将两个单词连在一起的情况(the_dead_return.txt中就有这种情况),所以我们提前替换掉。而冒号后如果接斜线的话,会认为这是一个网址从而什么都不做,否则的话就替换成空格。

(2)判断省略扩展名(.txt)的文件

  功能1是根据-s file.txt来输入的,我们在判断-s的时候可以建立一个分支,如果第一个参数不是-s时,就认为参数是不带扩展名的文件,代码如下。start()是接受装有文件名的列表而执行统计算法的程序。

else:
        inputfile=sys.argv[1]+'.txt'    
        inputfile_1.append(inputfile)
        start(inputfile_1)

(3)功能2的效果截图

3.功能3 支持命令行输入存储有英文作品文件的目录名,批量统计。

(1)判断参数是否为目录

  增添了功能3,所以前面的逻辑就要做出更改。现在的逻辑是,如果参数1是-s的话,就执行功能1的方法。否则的话就判断参数1是不是目录,如果是目录就执行功能3,否则执行功能二,具体代码如下。

def main(argv):
    path1=os.path.abspath('.')
    path1=path1.replace('\\','/')
    inputfile_1 = []
    a =[]
    #filepath = []
    for i in sys.argv:
        a.append(i)
    if sys.argv[1] == '-h':
        #print(sys.argv[1])
        print ('wf -s file.txt')
        print ('wf file')
        print ('wf dir')
        print ('wf -s < file.txt')
        sys.exit()
    elif sys.argv[1]=="-s":
        if(len(sys.argv)==3):
            inputfile_1.append(sys.argv[2])
            start(inputfile_1)
        else:
            s=''
            for line in sys.stdin:
                s=s+line
            start_chong(s)
    elif str(os.path.exists(sys.argv[1]))=='True':
        for root, dirs, files in os.walk(sys.argv[1]):
            for i in files:
                c = path1+'/'+sys.argv[1] +'/' + i
                #print(c)
                inputfile_1.append(c)
        start_file(inputfile_1)
    else:
        inputfile=sys.argv[1]+'.txt'    
        inputfile_1.append(inputfile)
        start(inputfile_1)

  os.path.exists()是用来判断是否为目录,而path1=os.path.abspath('.'),是获取当前执行程序所在路径,path1=path1.replace('\\','/')是将返回的路径中'\'替换成'/',因为python的路径是用'/',os.walk()则是用来获取目录相关信息的方法,files获取到目录内的文件名。c = path1+'/'+sys.argv[1] +'/' + i则是将目录内的文件名与程序所在地址正确拼接成绝对地址,传给inputfile_1列表再传给start_file(),start_file()是另外一个专门用来执行功能3的函数,因为功能3需要遍历所有目录内所有文件输出,还要输出文件的名(书名)。

def start_file(filename_list):
    for filename,j in zip(filename_list,filepath):
        print(j[:-4])
        string = wordcount(dispose_words(read_words(filename)))
        flag=1
        print('%-10s %-10s %-10s' %("total" , str(len(string)),'words'))
        print("")
        for i in string:
            if flag <11:
                #print(i)
                print('%-10s %-10s' %(i[0] , i[1]))
                flag += 1
        print("----")

  filepath列表是一个全局变量,用来存储功能3目录下所有的文件名,print(j[:-4])是一个不错的切片操作,可以将后四位,也就是.txt直接忽略掉,从而直接输出文件名(书名)。

(2)功能3的效果截图

4.功能4 支持命令行输入存储有英文作品文件的目录名,批量统计。

  功能4这里查了一些资料,没有完成像老师那样 -s < the_show_of_the_ring这样省略文件扩展名的指令,编写了支持文件全名的输入(带有文件扩展名)。在-s参数的判断中加入另外的分支,及判断参数的个数如果个数等于3个(argv[0]也算一个),则执行功能一,否则执行功能4。sys.stdin是python的重定向输入。start_chong()是针对执行功能4的函数。

elif sys.argv[1]=="-s":
        if(len(sys.argv)==3):
            inputfile_1.append(sys.argv[2])
            start(inputfile_1)
        else:
            s=''
            for line in sys.stdin:
                s=s+line
            start_chong(s)

功能4的效果截图

本项目的PSP

项目任务 计划花费的时间 实际用时
功能1 20 15
测试功能1 5 3
功能2 20 33
测试功能2 10 26
命令行参数输入,将功能1,2改为命令行参数输入 90 120
更改统计单词算法 20 15
测试统计单词算法 10 20
功能3 60 125
测试功能3 10 15
功能4 60 77
测试功能4 5 8
对每项功能重新进行测试 5 10

计划用时315min,实际用时467min

  开始时以为这是一个比较简单的程序,因为大学时候也有过类似的作业,没觉得很难,选择了python作为这次实验的编程语言,主要原因是最近在看数据分析的知识,用python处理的比较多,而且python集成了一些方法可以直接使用,写起来方便些。但实际做起来的时候却很困难,作业的要求很多,而且要求命令行参数输入,在这项上我计划的时间很多。对于功能1,2来说,实现起来并不麻烦,时间很短,但功能2我测试的是大文本,这让我的测试时间花费了很长世间。后面的命令行参数则学了很长时间,实际花费的时间比计划的多。后来发现开始统计的单词方法不完善,便重新写的方法,这里时间还算可以,功能3,和功能4,虽然分值不高,但有些困难,有的地方还要把以前写过的地方改变,这比我预计的时间多了很多。总结一下,测试的时候实际时间是比计划时间花费的多的,对于功能实现的难度要正确的估计,不然实际时间和计划时间会相差很多的。

代码版本及控制

地址见https://git.coding.net/silentteller/wf.git

 

推荐阅读