软工作业2-词频统计

发布时间:2019-05-27 22:20:21编辑:auto阅读(1904)

    软工作业2

                                                                                    ——实现一个能够对文本文件中的单词的词频进行统计的控制台程序

    1.Github地址:

    https://github.com/wangshiyaoyao/WordCont

    2.PSP表格

    PSP2.1

    Personal Software Process Stages

    预估耗时(分钟)

    实际耗时(分钟)

    Planning

    计划

       

    · Estimate

    · 估计这个任务需要多少时间

       

    Development

    开发

       

    · Analysis

    · 需求分析 (包括学习新技术)

    120

    360

    · Design Spec

    · 生成设计文档

    30

    30

    · Design Review

    · 设计复审

    10

    10

    · Coding Standard

    · 代码规范 (为目前的开发制定合适的规范)

    20

    20

    · Design

    · 具体设计

    30

    30

    · Coding

    · 具体编码

    120

    120

    · Code Review

    · 代码复审

    20

    40

    · Test

    · 测试(自我测试,修改代码,提交修改)

    120

    300

    Reporting

    报告

       

    · Test Repor

    · 测试报告

    60

    60

    · Size Measurement

    · 计算工作量

    30

    30

    · Postmortem & Process Improvement Plan

    · 事后总结, 并提出过程改进计划

    30

    30

     

    合计

    590

    1030

     

    3.需求分析

        实现一个能够对文本文件中的单词的词频进行统计的控制台程序

           功能实现:

           读取文件

                  获取文件名

                  判断获取参数是否正确

                  判断文件是否可读取,否则报错处理

           根据文件内容进行分析处理

                  统计字符个数

                  统计有效行数

                  统计词频

                         词频排序,获取前十

                  统计单词数

           输出结果

    测试用例:

           创建临时文件

    根据一定规则随机生成内容

    记录生成内容的有效单词等各种你参数

                         功能测试

                  测试统计字符个数

                  测试统计有效行数

                  测试统计词频

                  测试统计单词数

    难点:

           单词匹配,使用正则表达式,学习其语法

           测试套件的使用

           随机生成文件内容

    代码规范:

           使用python3.7+ pycharm

           单函数单功能

           添加注释,提高代码可读性

           代码符合pep8规范,使用pylint进行检查

           使用Profile进行性能检测

    4.代码设计

       Get_argv函数:获取并返回程序执行第一个参数,进行参数个数校验,有误返回空字符串,

       Main函数:接受一个文件名字符串,输出分析结果,无返回

            创建文件分析实例,进行分析,获取输出结果,进行输出

       _file_check函数:文件名检查,若不能打开并读取,进行报错,程序异常退出

       FileHandler类:

           __init__:初始化用于保存结果的各类变量,接受文件名,调用函数进行检查,调用分析函数进行分析

           _analysis:打开文件,读取内容,对读取内容调用欧冠具体函数进行分析,最后对词频排序

           _chars_analysis:字符统计,使用len函数

           _line_analysis:有效行统计,使用strip函数判断有效行

           _word_analysis:词频统计,调用单词检查函数获取合法单词,使用lower函数统一为小写

           _word_sum:单词数统计,调用单词检查函数获取合法单词

           _sort_conatiner:词频结果排序,取前十结果

           接口函数:

           chars:获取字符统计结果

           cotainer:获取词频前10统计结果

       lines:获取有效行统计结果

       words:获取单词数目统计结果

       _word_check_in_line:函数:获取字符串中合法单词,使用正则表达式匹配

     

       单元测试:

           创建临时文件

         根据一定规则随机生成内容

        记录生成内容的有效单词等各种你参数

           通过正则表达式反向匹配生成任意符合测试要求的内容,文件大小可控,覆盖较全面。

                         功能测试

                  测试统计字符个数

                  测试统计有效行数

                  测试统计词频

                  测试统计单词数

    5.关键功能实现

    文件检查:

    1 def _file_check(filename):
    2     """判断参数是一个可读文件,否则报错"""
    3     try:
    4         fd = open(filename)
    5         fd.close()
    6     except IOError:
    7         logging.error("Invalid argument.\nNeed a readable file.")
    8         sys.exit(1)

     

    对文件进行尝试可读打开,失败进行报错,并异常退出

    类初始化:

    def __init__(self, filename, encoding='utf-8'):
        self._chars = 0                                               # 统计ascii
        self._container = {}                                          # 统计词频
        self._lines = 0                                               # 统计行数
        self._words = 0                                               # 统计单词数
        self._sorted_container = []                                   # 输出词频
        _file_check(filename)
        self._analysis(filename, encoding)

     

    使用字典进行词频统计,避免重复

    文件默认使用utf-8打开

    词频统计:

    1 def _word_analysis(self, line):
    2     """统计词频"""
    3     for word_match in _word_check_in_line(line):
    4         word = word_match.lower()
    5         self._container[word] = self._container.get(word, 0) + 1

     

    使用字典的get函数对初次添加做特殊初始化

    合法单词检查:

    1 def _word_check_in_line(line):
    2     """单词匹配"""
    3     pattern = r'(?<![a-zA-Z0-9])([a-zA-Z][0-9a-zA-Z]*)'
    4     result = re.findall(pattern, line)
    5     # logging.debug('word check in line result:%s', result)
    6     return result

     

           使用正则进行检查

           匹配字符开头后跟任意长度字符或数字,单词前一字符不为字母数字

           使用findall函数获取所有合法单词,以列表存储

     

    词频结果处理:

    1 def _sort_container(self):
    2     """词频结果排序,获取前10结果"""
    3     self._sorted_container = sorted(self._container.items(), key=lambda x: (-x[1], x[0]))[:10]

     

           使用sorted函数对字典进行排序

        参数:key=lambda x: (-x[1], x[0])

               表示排序依据,先根据字典值大->小排序,后根据字典键按字典序排序

               [:10]:表示取前十个结果

     

    生成用于测试的临时文件:

     1 def touch_test_file(line_num, word_num):
     2     """创建测试文件,随机生成字符,用于测试"""
     3 
     4     _x = Xeger()
     5     words = lambda: _x.xeger(r'[a-zA-Z][a-zA-Z0-9]*')       # 随机生成有效单词
     6     non_word = lambda: _x.xeger(r'\d[a-zA-Z0-9]*')          # 随机生成开头为数字的单词
     7     separator = lambda: _x.xeger(r'[^a-zA-Z0-9\n\r]')       # 随机生成非字母数字回车换行符的字符
     8     space = lambda: _x.xeger(r'\n[\s]*\n')                  # 随机生成回车空白字符回车
     9 
    10     # 统计生成的文件中字符、单词、有效行、词频
    11     result = {'chars': 0, 'words': word_num * line_num, 'lines': line_num, 'container': {}}
    12 
    13     # 创建文件,随机生成字符
    14     fd = open(temp_file, 'w')
    15     for line in range(line_num):
    16         for i in range(word_num):
    17             word = words()
    18             chars = word + separator() + non_word() + separator()
    19             result['chars'] += len(chars)
    20             result['container'][word.lower()] = result['container'].get(word.lower(), 0) + 1
    21             fd.write(chars)
    22         chars = space()
    23         result['chars'] += len(chars)
    24         fd.write(chars)
    25     fd.close()
    26 
    27     # 获取排序后的词频结果
    28     sort_result = sorted(result['container'].items(), key=lambda x: (-x[1], x[0]))[:10]
    29     result['container'] = sort_result
    30     return result

    使用第三方库xeger,反向生成符合正则的任意字符串

    创建好要生成的合法非法单词,字符,空白字符等

    创建临时文件,随机生成字符串写入

    将结果返回

    6.代码风格说明

      Unused variable 'line' (unused-variable):未使用的参数:for循环中,使用_代替

      Trailing newlines (trailing-newlines):文件末尾多余空行,删除

    7.运行结果

     

    测试运行:

     

    8.性能分析结果及改进

    使用cProfile

             182480 function calls (182313 primitive calls) in 0.207 seconds

     

       Ordered by: internal time

     

       ncalls  tottime  percall  cumtime  percall filename:lineno(function)

         7382    0.057    0.000    0.057    0.000 {method 'findall' of 're.Pattern' objects}

            1    0.049    0.049    0.057    0.057 {built-in method builtins.sorted}

         3691    0.025    0.000    0.074    0.000 WordCont.py:64(_word_analysis)

            1    0.010    0.010    0.190    0.190 WordCont.py:74(_analysis)

        40049    0.008    0.000    0.008    0.000 {method 'get' of 'dict' objects}

        33382    0.008    0.000    0.008    0.000 WordCont.py:87(<lambda>)

        40006    0.006    0.000    0.006    0.000 {method 'lower' of 'str' objects}

         7385    0.005    0.000    0.010    0.000 re.py:271(_compile)

           13    0.004    0.000    0.004    0.000 {built-in method builtins.print}

         7382    0.004    0.000    0.068    0.000 re.py:215(findall)

         3691    0.004    0.000    0.040    0.000 WordCont.py:70(_word_sum)

         7382    0.004    0.000    0.072    0.000 WordCont.py:25(_word_check_in_line)

     

            1    0.003    0.003    0.060    0.060 WordCont.py:85(_sort_container)

         7681    0.002    0.000    0.002    0.000 {built-in method builtins.isinstance}

         3691    0.002    0.000    0.003    0.000 WordCont.py:59(_line_analysis)

           57    0.002    0.000    0.002    0.000 {built-in method nt.stat}

         3691    0.002    0.000    0.002    0.000 WordCont.py:55(_chars_analysis)

           10    0.002    0.000    0.002    0.000 {built-in method marshal.loads}

         3691    0.001    0.000    0.001    0.000 {method 'strip' of 'str' objects}

           10    0.001    0.000    0.001    0.000 {method 'read' of '_io.FileIO' objects}

    7762/7732    0.001    0.000    0.001    0.000 {built-in method builtins.len}

           45    0.001    0.000    0.002    0.000 {built-in method builtins.__build_class__}

            1    0.001    0.001    0.207    0.207 WordCont.py:8(<module>)

               19/4    0.001    0.000    0.002    0.000 sre_parse.py:475(_parse)

    …… …… ……

           Findall函数耗时最多,sorted其次,内建函数暂无法优化。

     

    按执行次数分析:

             182480 function calls (182313 primitive calls) in 0.216 seconds

     

       Ordered by: call count

     

       ncalls  tottime  percall  cumtime  percall filename:lineno(function)

        40049    0.009    0.000    0.009    0.000 {method 'get' of 'dict' objects}

        40006    0.006    0.000    0.006    0.000 {method 'lower' of 'str' objects}

        33382    0.008    0.000    0.008    0.000 WordCont.py:87(<lambda>)

    7762/7732    0.001    0.000    0.001    0.000 {built-in method builtins.len}

         7681    0.003    0.000    0.003    0.000 {built-in method builtins.isinstance}

         7385    0.006    0.000    0.011    0.000 re.py:271(_compile)

         7382    0.004    0.000    0.076    0.000 WordCont.py:25(_word_check_in_line)

     

         7382    0.004    0.000    0.072    0.000 re.py:215(findall)

         7382    0.058    0.000    0.058    0.000 {method 'findall' of 're.Pattern' objects}

         3691    0.002    0.000    0.003    0.000 WordCont.py:55(_chars_analysis)

         3691    0.002    0.000    0.003    0.000 WordCont.py:59(_line_analysis)

         3691    0.025    0.000    0.078    0.000 WordCont.py:64(_word_analysis)

         3691    0.004    0.000    0.042    0.000 WordCont.py:70(_word_sum)

         3691    0.001    0.000    0.001    0.000 {method 'strip' of 'str' objects}

          411    0.000    0.000    0.000    0.000 sre_parse.py:233(__next)

           执行次数最多代码:get函数,lower函数

     

    按函数运行时间分析:

             182480 function calls (182313 primitive calls) in 0.201 seconds

     

       Ordered by: cumulative time

     

       ncalls  tottime  percall  cumtime  percall filename:lineno(function)

         12/1    0.000    0.000    0.201    0.201 {built-in method builtins.exec}

            1    0.001    0.001    0.201    0.201 WordCont.py:8(<module>)

            1    0.000    0.000    0.190    0.190 WordCont.py:115(main)

            1    0.000    0.000    0.188    0.188 WordCont.py:46(__init__)

            1    0.010    0.010    0.187    0.187 WordCont.py:74(_analysis)

         3691    0.023    0.000    0.073    0.000 WordCont.py:64(_word_analysis)

         7382    0.004    0.000    0.070    0.000 WordCont.py:25(_word_check_in_line)

     

         7382    0.004    0.000    0.066    0.000 re.py:215(findall)

            1    0.003    0.003    0.060    0.060 WordCont.py:85(_sort_container)

            1    0.050    0.050    0.057    0.057 {built-in method builtins.sorted}

         7382    0.055    0.000    0.055    0.000 {method 'findall' of 're.Pattern' objects}

         3691    0.004    0.000    0.039    0.000 WordCont.py:70(_word_sum)

         14/3    0.000    0.000    0.012    0.004 <frozen importlib._bootstrap>:978(_find_and_load)

         14/3    0.000    0.000    0.011    0.004 <frozen importlib._bootstrap>:948(_find_and_load_unlocked)

         14/3    0.000    0.000    0.011    0.004 <frozen importlib._bootstrap>:663(_load_unlocked)

         10/3    0.000    0.000    0.010    0.003 <frozen importlib._bootstrap_external>:722(exec_module)

         18/3    0.000    0.000    0.010    0.003 <frozen importlib._bootstrap>:211(_call_with_frames_removed)

         7385    0.005    0.000    0.009    0.000 re.py:271(_compile)

           运行时间最多函数main函数,__init__初始化函数。

    附:

    ncalls:表示函数调用的次数;
    tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
    percall:(第一个percall)等于 tottime/ncalls;
    cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
    percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
    filename:lineno(function):每个函数调用的具体信息;

    性能分析图:

     

关键字