本章主要介绍文本分析的算法设计过程中会用到的一些技巧,我只把书中对我来说有意思的例子拿出来了。
一 递归
递归就是循环的一种,为了实现某种目的反复调用自身。下面这个例子的有意思的地方不仅限于迭代,还用了yield,可以参考廖雪峰老师关于Yield的解释https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/。为了让大家理解yield,我添加了Print函数。
# 迭代
def permutations(seq):
if len(seq) <= 1:
print(seq)
yield seq # 如果序列长度为1,无需操作直接返回自身
else:
for perm in permutations(seq[1:]): # 如果序列不为1,调用函数生成[1:]的所有排序方法
for i in range(len(perm)+1): # 将第一位数循环放到后几位数的中间,生成多种排序组合
print(perm[:i] + seq[0:1] + perm[i:])
yield perm[:i] + seq[0:1] + perm[i:]
list(permutations(['a','b', 'c']))
结果如下
['c']
['b', 'c']
['a', 'b', 'c']
['b', 'a', 'c']
['b', 'c', 'a']
['c', 'b']
['a', 'c', 'b']
['c', 'a', 'b']
['c', 'b', 'a']
我们来分析一下整个程序如何运行的
第一步 开始permutations(['a','b', 'c']),因为seq长度为3所以 程序跑到了 for perm in permutations(seq[1:])这里,开始第一次循环
第二步 开始permutations(['b', 'c']),因为seq长度为2所以 程序跑到了 程序跑到了 for perm in permutations(seq[1:])这里,开始第一次循环
第三步 开始开始permutations(['c']),因为seq长度1,所以yield seq,同时打印了['c']
第四步 这里yield出来的['c'],其实是第二步程序中需要的结果,所以返回第二步程序中,开始for perm in [['c']]
第五步 重点来了yield的效果开始体现,for i in range(2) 其实会有2个序列生成[['b', 'c'],['c', 'b']],但是yield是惰性的。程序只会跑出一个['b', 'c'],同时打印出['b', 'c']
第六步 开始第一步的程序,因为 perm现在等于['b', 'c']了,所以开始把a依次放入 bc前面,中间和后面。结果依次yield['a', 'b', 'c']['b', 'a', 'c']['b', 'c', 'a'],这里yield也是惰性的,但是list(permutations(['a','b', 'c']))这个了,所以结果都出来了
第七步 开始for perm in permutations(seq[1:])这里,开始第二次循环,又调用了permutations(['b', 'c']),这时候惰性的yield会从上一次结束的地方继续运行,吐出第二结果['c', 'b']
第八步 把a 依次放入['c', 'b'] 前中后,吐出结果
如果把程序中的yield换成return,是会报错的。
二 建立辅助的数据结构
有时候原始数据并不方便我们实现某一种功能,这时候不妨在原始数据基础上建立一个辅助的数据结构。比如下面的例子,如果在大量文章中找出某些词的上下文呢?不妨就先建立索引,保存着每个词出现过在哪些文章中。原书中是通过open函数直接开打zip文件夹中的大量电影评论数据,但是我python3不行我又懒得解决,所以我直接把zip中的文件都解压出来了,这样直接open就没问题了。
# 建立辅助数据结构
def raw(file): #打开文件去除符号
contents = open(path + r'\\' + file, 'r').read()
contents = re.sub(r'<.*?>', ' ', contents)
contents = re.sub('\s+', ' ', contents)
return contents
def snippet(doc, term): # 打印结果
text = ' '*30 + raw(doc) + ' '*30 # 防止文本过短打印报错
pos = text.index(term) # 获取term的第一个index
return text[pos-30:pos+30] # 打印上下文
print ("Building Index...")
import os
path = r'C:\Users\BF\AppData\Roaming\nltk_data\corpora\movie_reviews\neg'
files = os.listdir(path) # 获取该文件下的文件名
# files = nltk.corpus.movie_reviews.abspaths()
idx = nltk.Index((w, f) for f in files for w in raw(f).split()) # 建立辅助数据结构,结果就是字典每个词属于哪些文章
# 这里是根据你输入的词实时打印结果
query = ''
while query != "quit": # 输入quit则退出当前程序
query = input("query> ") # 捕捉你当前获取的词
if query in idx: #首先判断这个词是不是当前文本中包含的
for doc in idx[query]: # 找出这个词所在的文章
print (snippet(doc, query)) # 打印这个词上下文
else:
print ("Not found")
循行程序后就可以在开始输入你想查询的词了。
三 动态规划
用于解决多个重叠子问题的问题,解决的核心思想就是不能重复的计算每个子问题,而是应该将子问题的结果存储在一个查找表里。有两种音节SL,S占1个长度,L占2个长度,问有多少组合方式能组成20个长度。
# 硬递归,不做任何过程数据的存储,最慢
def virahanka1(n):
if n == 0:
return [""]
elif n == 1:
return ["S"]
else:
s = ["S" + prosody for prosody in virahanka1(n-1)]
l = ["L" + prosody for prosody in virahanka1(n-2)]
return s + l
# 将过程数据apend到一个list中
def virahanka2(n):
lookup = [[""], ["S"]]
for i in range(n-1):
s = ["S" + prosody for prosody in lookup[i+1]]
l = ["L" + prosody for prosody in lookup[i]]
lookup.append(s + l)
return lookup[n]
# 将过程数据存储到一个字典中
def virahanka3(n, lookup={0:[""], 1:["S"]}):
if n not in lookup:
s = ["S" + prosody for prosody in virahanka3(n-1)]
l = ["L" + prosody for prosody in virahanka3(n-2)]
lookup[n] = s + l
return lookup[n]
from nltk import memoize
# 使用装饰器将函数的过程数据保存下来
@memoize # 装饰器用于存储迭代过程中产生的值
def virahanka4(n):
if n == 0:
return [""]
elif n == 1:
return ["S"]
else:
s = ["S" + prosody for prosody in virahanka4(n-1)]
l = ["L" + prosody for prosody in virahanka4(n-2)]
return s + l
virahanka1(10)
virahanka2(10)
virahanka3(10)
virahanka4(20)
代码中涉及到一个装饰器的概念,本章调用的装饰器是存储中间结果,还有各式各样的装饰器。装饰器可以理解成
这样memoize (virahanka4(20)),简单把他想成一个函数或者一个类,具体如何写一个装饰器可以参考https://blog.csdn.net/xiangxianghehe/article/details/77170585