写作目标:总结实际编写中的一些问题。
分析
给出一个待查询的字符串str
比如 ‘app is apple.’,一个被匹配的字符串match
比如 ‘apple’。
朴素字符串匹配的算法基本思想很简单(也很暴力),一个指针i
指向字符串str的第一个字符,一个指针j
指向字符串match的第一个字符,假如指针i
和指针j
指向的字符相同,指针i
和指针j
同时移动,否则指针i
移动到‘之前位置的下一个位置’(稍后在代码中解释),指针j
移动到字符串match的0位置。
两个指针遍历两个字符串,很容易写出下面的代码:
for i in range(0, len(str)):
for j in range(0, len(match)):
if(str[i] != str[j]):
#...
不过写完就会发现,这段代码只是将str中的一个字符与match中的每一个字符进行比较,完全不符合我们的逻辑。
之后写下了这样的代码:
for i, j in range(0, len(str)):
一方面,i
j
可能会超出str或match字符串数组的长度,另一方面,更重要的是,这里的逻辑是同时依次比较str和match中的字符,即同时比较它们的第一个字符、第二个字符……也并非我们想要的逻辑。
我们是需要同时依次比较str和match中的字符的,但同时还需要控制从str的不同位置‘开始’进行对比:
for i in ...:
if(str[i] != match[j]):
...
这里要注意的是指向str的指针和指向match的指针移动在有些情况不是同时移动的。
最终代码
def find(str, match):
i = 0
while(i < len(str) - len(match) + 1):
# 注意点是只需要遍历到len(str) - len(match)位置
j = 0
while(j < len(match)):
if(str[i] != match[j]):
# 当前字符不相同,退出当前循环
break
else:
# 当前字符相同
if(j == len(match) - 1):
# 如果j来到了match数组的最后一位
print('get')
return
else:
# 判断下一个字符
i += 1
j += 1
# 发生字符不匹配,只需要i移动到上一轮的位置的下一个位置,即‘之前位置的下一个位置’
# 接下来会执行j=0,让j指向match字符串数组的0位置
# (i j 没有同时移动)
i += 1
# i来到最后位置
print('not found')
return
find("app is apple.", "apple")
进一步,可以将两层循环优化为单层:
def find(str, match):
i = 0
j = 0
while(i < len(str) and j < len(match)):
# 这里i的范围是整个str
if(str[i] != match[j]):
# 当前字符不相同
# 发生字符不匹配,只需要i向前移动
# j=0,让j指向match字符串数组的0位置
i += 1
j = 0
else:
# 当前字符相同
if(j == len(match) - 1):
# 如果j来到了match数组的最后一位
print('get')
return
else:
# 判断下一个字符
i += 1
j += 1
# i来到最后位置
print('not found')
return
find("app is apple.", "apple")
总结
初学时很容易理解的方法,写起来还是有很多和‘想的’不一样的细节。切忌眼高手低。
另外对文中的一些描述作一些补充:
- 字符串str,也可以称为字符串数组,这个数组中的每个元素是一个字符
- 字符串str的第一个字符,也就是字符串数组0位置上的字符
i
和j
这两个在文中称为指针,它们的值是数值,这个数值记录的是‘当前’i在字符串数组str的位置和j在字符串数组match的位置- 对于这两个指针有时称为‘移动’到某个位置,有时称‘指向’第几个字符,它们是同样的意思,比如
j
移动到字符串数组match的0位置与j
指向字符串match的第一个字符,j
的变化是相同的