软工作业2-词频统计
软工作业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):每个函数调用的具体信息;
性能分析图: