Python如何判断一个生成器是另一个的前缀或连续子序列
未知长度生成器的连续子序列判断
我有两个长度未知的生成器A和B,需要判断B是否是A的连续子序列,最初实现代码如下:
def subseq(A, B): b0 = next(B) for a in A: if a == b0: break else: # 循环未触发break # A中找不到B的首元素,B一定不是A的子序列 return False # 判断B剩余部分是否是A剩余部分的前缀 return prefix(A, B) def prefix(A, B): return all(a == b for a, b in zip(A, B))
上述实现中的prefix(A, B)函数存在逻辑缺陷:如果A剩余元素长度短于B剩余元素长度,会出现假阳性误判:
例如当A = 'abc'、B = 'abcd'(将二者视作生成器)时,return all(a == b for a, b in zip(A, B))会错误返回True。
但如果替换为zip_longest实现,又会出现反向的假阴性误判问题:
例如当A = 'abcd'、B = 'abc'时,return all(a == b for a, b in zip_longest(A, B))会错误返回False。
实现要求
需要实现按第二个参数的长度执行zip截断的逻辑,效果类似自定义函数zip_(A, B, ziplengthselect=1):
- 参数
ziplengthselect=i的作用是指定函数按第i个传入参数的长度执行zip截断。
在该自定义zip函数的基础上,表达式all(a == b for a, b in zip_(A, B, fillvalue=sentinel, ziplengthselect=1))(其中sentinel是B中不会出现的哨兵值)可实现如下预期行为: - 遍历到
B的末尾时,表达式返回True - 先遍历到
A的末尾时,函数会填充哨兵值,此时判断sentinel == b会失败(因为哨兵值选取为B中不存在的值),返回False - 任意位置出现
a == b判定不通过时,表达式返回False
已尝试的实现
目前已有基于try/except块的实现方案,希望了解是否存在更优雅的实现方式。
第一种prefix函数实现:
# 判断生成器B是否是生成器A的前缀 def prefix(A, B): for b in B: try: a = next(A) if a != b: return False except StopIteration: # 已遍历到A的末尾,A长度不足 return False return True
第二种prefix函数实现:
# 判断生成器B是否是生成器A的前缀 def prefix(A, B): prefix_match = all(a == b for a, b in zip(A, B)) if not prefix_match: return False try: next(B) # B仍有剩余元素,说明A长度不足 return False except StopIteration: # B已遍历完成,匹配成功 return True
支持重复元素的修正版本
上述代码在A无重复元素时可正常运行,但如果A存在重复元素,需要通过tee对生成器做拷贝处理,修正后的完整代码如下:
from itertools import tee def subseq(A, B): try: b0 = next(B) except StopIteration: return True while True: try: a = next(A) if a == b0: A, Acop = tee(A) B, Bcop = tee(B) if prefix(Acop, Bcop): return True del Acop, Bcop except StopIteration: return False def prefix(A, B): for b in B: try: a = next(A) if a != b: return False except StopIteration: # 已遍历到A的末尾,长度不足 return False return True # 测试用例 A = (i for i in range(10)) B = (i for i in range(5,8)) print(subseq(A, B)) # 输出True A = (i for i in range(10)) B = (i for i in range(5,11)) print(subseq(A, B)) # 输出False A = (i for i in [1,2,3]*10 + [1,2,3,4]) B = (i for i in [1,2,3]) print(subseq(A, B)) # 输出True A = (i for i in [1,1,2,1,1,2]*8 + [3]) B = (i for i in [1,1,2,3]) print(subseq(A, B)) # 输出True
参考:列表场景实现
列表场景下的同类连续子序列判断实现更简单,因为可以直接获取列表长度,对应代码如下:
def isSublist(lst, sublst): N, M = len(lst), len(sublst) starts = (i for i in range(N - M + 1) if lst[i] == sublst[0]) for i in starts: # i <= N - M,因此N - i >= M,不会出现索引越界 j = 0 while j < M and lst[i] == sublst[j]: i += 1 j += 1 if j == M: return True return False
内容的提问来源于stack exchange,提问作者joseville
相关产品推荐
相关产品推荐

