Python Cookbook-5.13 寻找子序列
任务
需要在某大序列中查找子序列。
解决方案
如果序列是字符串(普通的或者Unicode),Python 的字符串的 find 方法以及标准库的re模块是最好的工具。否则,应该使用Knuth-Morris-Pratt算法(KMP):
def KnuthMorrisPratt(text,pattern):
'''在序列text中找到pattern的子序列的起始位置
每个参数都可以是任何可迭代对象
在每次产生一个结果时,对text的读取正好到达(包括)对pattern的一个匹配'''
#确保能对pattern进行索引操作,同时制作pattern的一个拷贝,以防在生成结果时意外地修改pattern
pattern = list(pattern)
length = len(pattern)
#创建KMP"移量表"并命名为shifts
shifts = [1]*(length + 1)
shift = 1
for pos,pat in enumerate(pattern):
while shift <= pos and pat != pattern[pos - shift]:
shift += shifts[pos - shift]
shifts[pos + 1] = shift
#执行真正的搜索
startPos = 0
matchLen = 0
for c in text:
while matchLen == length or matchLen >= 0 and pattern[matchLen] != c:
startPos += shifts[matchLen]
matchLen -= shifts[matchLen]
matchLen += 1
if matchLen == length:yield startPos
讨论
本节实现的 Knuth-Morris-Pratt 算法可被用于在一个大文本的连续序列中查找某种指定的模式。由于 KMP 是以顺序的方式访问文本,所以很自然地,我们也可以将其应用于包括文本在内的任意可迭代对象。在处理阶段,算法会创建一个关于偏移量的表,它消耗的时间正比于模式的长度,而每个文本标志则以恒定的时间处理。在所有关于文本处理的基础算法书中都可以看到 KMP 算法的解释和示例。(更多资料一栏中也提供了推荐读物。)
如果 text 和 pattern 都是 Python 字符串,可以通过使用 Python 的内建搜索方法得到一个更快的方案:
def finditer(text,pattern):
pos = -1
while True:
pos = text.find(pattern,pos+1)
if pos <0:break
yield pos
比如,使用一个长度为4的字母表(“ACGU”),在长度为100000的文本中查找长度为8的一个模式,在我的计算机上,借助 finditer 函数耗时为 4.3ms,但使用 KnuthMorrisPrat函数执行同样任务则需要540ms(在 Python2.3 中;而在 Python2.4 会快一些,约 480ms但仍然比 finditer 慢了超过 100倍)。所以请记住:本节的算法适用于在通用的序列中进行搜索,包括那些数据量大到无法放入内存的情况,如果只需要对字符串搜索Python 内建的搜索方法具有完全压倒性的优势。