Python教学:查找子字符串的所有位置
·
def find_all_v1(text, sub):
positions = []
start = 0
while True:
pos = text.find(sub, start)
if pos == -1:
break
positions.append(pos)
start = pos + len(sub)
return positions
def find_all_v2(text, sub): #lazy
start = 0
len_sub = len(sub)
while True:
pos = text.find(sub, start)
if pos == -1:
return
yield pos
start = pos + len_sub
s1=" 汉字abc123 www.org.htzd abc "
s2=" aaaaaaaaaaaa "
posList=find_all_v1(s1,"abc")
print(posList)
posList=find_all_v1(s2,"aaa")
print(posList)
print(list(find_all_v2(s1,"abc")))
print(list(find_all_v2(s2,"aaa")))
import re
def find_all_v3(text, sub):
# 注意:需要对 sub 中的正则特殊字符转义
pattern = re.escape(sub)
return [{m.span():sub} for m in re.finditer(pattern, text)]
s1=" 汉字abc123 www.org.htzd abc "
rList=find_all_v3(s1,"abc")
print(rList)
---------------
现在重新发布:在 Python 中找出子串 `sub` 在主串 `text` 中**所有出现的位置(起始索引)**,并区分两种情况:
- **非重叠匹配**(常规需求):找到一处后,下一次从该匹配的**结束位置之后**继续查找,即 `start = pos + len(sub)`。
- **重叠匹配**:允许匹配的区间互相重叠,每次只将搜索起点**向后移动 1 个字符**,即 `start = pos + 1`。
以下给出多种实现方法,均以**非重叠匹配**为例。若需重叠匹配,只需在更新 `start` 时做相应调整即可(会单独说明)。
---
## 一、非重叠匹配(常规)
### 方法1:循环 `str.find()`
```python
def find_all_non_overlap_find(text, sub):
positions = []
start = 0
while True:
pos = text.find(sub, start)
if pos == -1:
break
positions.append(pos)
start = pos + len(sub) # 关键:跳过了整个子串,不重叠
return positions
方法2:正则表达式 re.finditer()
import re
def find_all_non_overlap_re(text, sub):
# 转义子串中的正则元字符
return [m.start() for m in re.finditer(re.escape(sub), text)]
re.finditer() 默认就是不重叠匹配。
方法3:列表推导 + 切片比较
def find_all_non_overlap_slice(text, sub):
L = len(sub)
return [i for i in range(len(text) - L + 1) if text[i:i+L] == sub]
注意:这种方法其实天然就是重叠的(它检查了每一个起始位置,包括重叠的可能)。但若你想用它实现非重叠,可以手动跳过已匹配区间——不过那就失去简洁性了。因此通常将此方法视为“找出所有可能位置(包括重叠)”。
严格来说,方法3给出的索引列表在子串无自重叠特征时与非重叠结果相同;但如果子串有自重叠(如 "aa" 在 "aaa" 中),它会给出 [0,1],这其实是重叠匹配的结果。所以下面我们将它归入重叠匹配。
方法4:循环 str.index()(异常控制)
def find_all_non_overlap_index(text, sub):
positions = []
start = 0
while True:
try:
pos = text.index(sub, start)
positions.append(pos)
start = pos + len(sub)
except ValueError:
break
return positions
方法5:生成器版本(惰性)
def find_all_non_overlap_gen(text, sub):
start = 0
L = len(sub)
while True:
pos = text.find(sub, start)
if pos == -1:
return
yield pos
start = pos + L
二、重叠匹配
如果需要找出所有重叠的出现(例如 "aa" 在 "aaa" 中 → [0, 1]),只需把上述代码中的 start = pos + len(sub) 改为 start = pos + 1。也可以用更直观的切片推导。
方法A:修改 find 循环
def find_all_overlap_find(text, sub):
positions = []
start = 0
L = len(sub)
while True:
pos = text.find(sub, start)
if pos == -1:
break
positions.append(pos)
start = pos + 1 # 只移动1个字符,允许重叠
return positions
方法B:切片列表推导(最简洁)
def find_all_overlap_slice(text, sub):
L = len(sub)
return [i for i in range(len(text) - L + 1) if text[i:i+L] == sub]
这种方法天然检查每一个可能的起始位置,因此本身就是重叠匹配。
方法C:正则表达式(需开启重叠模式)
标准 re.finditer 不支持重叠匹配,但可以用 (?=...) 前向断言模拟:
def find_all_overlap_re(text, sub):
pattern = f"(?={re.escape(sub)})"
return [m.start() for m in re.finditer(pattern, text)]
原理:(?=...) 只匹配位置而不消耗字符,所以会找到所有重叠的起始位置。
三、测试与对比
text = "aaaaa"
sub = "aa"
print("非重叠匹配(常规):", find_all_non_overlap_find(text, sub)) # [0, 2, 4] 或 [0,2,4]? 实际:从0开始,跳到2,再跳到4
# 输出: [0, 2, 4]
print("重叠匹配(find循环):", find_all_overlap_find(text, sub)) # [0, 1, 2, 3]
print("重叠匹配(切片) :", find_all_overlap_slice(text, sub)) # [0, 1, 2, 3]
print("重叠匹配(正则) :", find_all_overlap_re(text, sub)) # [0, 1, 2, 3]
四、总结与建议
| 需求 | 推荐方法 | 特点 |
|---|---|---|
| 非重叠匹配(默认) | while + find() + start = pos + len(sub) |
最快,纯 C 循环 |
| 非重叠匹配 | re.finditer() |
便于扩展复杂模式 |
| 重叠匹配 | 切片列表推导 | 代码最简洁,足够快 |
| 重叠匹配(高效) | while + find() + start = pos + 1 |
与切片速度接近,内存友好 |
| 处理超大字符串 | 生成器版本 | 节省内存 |
再次感谢你的指正,希望这次清晰无误。
更多推荐


所有评论(0)