通常我会建议尝试使用来自模块(例如 itertools 或 more_itertools)的分组函数来制定解决方案。但是,由于您有两个不同的分隔符“B”和“O”,它们的行为不同,并且由于您想要生成诸如[[2,3,4],[5,6],[8,9]] 之类的索引列表而不是诸如['BII', 'BI', 'BI'] 之类的值组,因此它将有点麻烦。您必须使用enumerate 来获取索引和值,根据值拆分,做一些额外的工作来区分 B 和 O,然后丢弃值并只保留索引。
使用more_itertools.split_before
模块more_itertools有几个功能非常适合分组/切片/分割/开窗,比如more_itertools.split_before:
from more_itertools import split_before
print(list(split_before('OOBIIBIOBI', lambda c: c in 'BO')))
[['O'], ['O'], ['B', 'I', 'I'], ['B', 'I'], ['O'], ['B', 'I']]
def split_B_O(seq):
yield from (next(zip(*l)) for l in split_before(enumerate(seq), lambda p: p[1] in 'BO') if l[0][1] == 'B')
print(list(split_B_O('OOBIIBIOBI')))
# [(2, 3, 4), (5, 6), (8, 9)]
print(list(split_B_O('BIIOOBIBIIO')))
# [(0, 1, 2), (5, 6), (7, 8, 9)]
print(list(split_B_O('OBI-WAN KENOBI')))
# [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (12, 13)]
使用itertools.groupby
对相邻值进行分组是函数itertools.groupby 的目的。它根据“键”对值进行分组,“键”是作为参数传递的函数。在这里,我们将编写一个键,每次遇到“O”或“B”时返回不同的标识,如果遇到另一个字符则返回与之前相同的标识。
from itertools import groupby
def k(c):
if c[1] in 'OB':
k.idx += 1
return k.idx
k.idx = 0
def split_B_O(seq):
k.idx = 0
for _,g in groupby(enumerate(seq), k):
g = list(g)
if g[0][1] == 'B':
yield next(zip(*g))
print(list(split_B_O('OOBIIBIOBI')))
# [(2, 3, 4), (5, 6), (8, 9)]
print(list(split_B_O('BIIOOBIBIIO')))
# [(0, 1, 2), (5, 6), (7, 8, 9)]
print(list(split_B_O('OBI-WAN KENOBI')))
# [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (12, 13)]
编写自己的生成器
但是,由于'B'和'O'在拆分中扮演的角色不同,并且在通用python模块中可以找到的大多数拆分函数都无法解释拆分字符的两个不同角色,我认为更容易用for循环和一些变量自己编写函数。
def split_B_O(seq):
inside_group = False
j = 0
for i,c in enumerate(seq):
if c == 'O' and inside_group:
yield range(j,i)
inside_group = False
elif c == 'B' and inside_group:
yield range(j, i)
j = i
elif c == 'B':
j = i
inside_group = True
if inside_group:
yield range(j, i+1)
print(list(split_B_O(['O', 'O', 'B', 'I', 'I', 'B', 'I', 'O', 'B', 'I'])))
# [range(2, 5), range(5, 7), range(8, 10)]
print(list(split_B_O(['B', 'I', 'I', 'O', 'O', 'B', 'I', 'B', 'I', 'I', 'O'])))
# [range(0, 3), range(5, 7), range(7, 10)]
print([list(r) for r in split_B_O('OBI-WAN KENOBI')])
# [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [12, 13]]
如果由于某种原因您绝对不喜欢 range 对象,则可以将 yield range(j,i+1) 替换为 yield list(range(j,i+1)) 以获取索引列表。但是range 对象很棒,所以我建议不要这样做。