考虑输入字符串:
mystr = "just some stupid string to illustrate my question"
以及指示输入字符串分割位置的字符串列表:
splitters = ["some", "illustrate"]
输出应该看起来像
result = ["just ", "some stupid string to ", "illustrate my question"]
我编写了一些实现以下方法的代码。对于splitters
中的每个字符串,我在输入字符串中找到它的出现,并插入一些我知道肯定不会是我的输入字符串的部分(例如,这个'!!'
)。然后我使用刚刚插入的子字符串拆分字符串。
for s in splitters:
mystr = re.sub(r'(%s)'%s,r'!!\1', mystr)
result = re.split('!!', mystr)
这个解决方案看起来很难看,有没有更好的方法呢?
答案 0 :(得分:5)
使用re.split
拆分将始终从输出中删除匹配的字符串( NB,这不完全正确,请参阅下面的编辑)。因此,您必须使用正向前瞻性表达式((?=...)
)进行匹配而不删除匹配项。但是,re.split
会忽略空匹配,因此仅使用超前表达式并不起作用。相反,你至少会在每次拆分时丢失一个字符(甚至尝试用#34}来欺骗re
边界"匹配(\b
)不起作用) 。如果你不关心在每个项目的末尾丢失一个空白/非单词字符(假设你只分成非单词字符),你可以使用类似
re.split(r"\W(?=some|illustrate)")
会给出
["just", "some stupid string to", "illustrate my question"]
(请注意just
和to
之后的空格不足。然后,您可以使用str.join
以编程方式生成这些正则表达式。请注意,每个拆分标记都使用re.escape
进行转义,因此splitters
项中的特殊字符不会以任何不需要的方式影响正则表达式的含义(例如,{{1}在其中一个字符串中,否则会导致正则表达式语法错误。)
)
修改( HT转为@Arkadiy ):对实际匹配进行分组,即使用the_regex = r"\W(?={})".format("|".join(re.escape(s) for s in splitters))
代替(\W)
,返回作为单独项目插入列表的非单词字符。然后,连接每两个后续项目也将根据需要生成列表。然后,您还可以使用\W
代替(.)
来删除使用非单词字符的要求:
\W
由于普通文本和辅助字符交替显示,the_new_regex = r"(.)(?={})".format("|".join(re.escape(s) for s in splitters))
the_split = re.split(the_new_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest(the_split[::2], the_split[1::2], fillvalue='')]
包含正常的拆分文本和the_split[::2]
辅助字符。然后,the_split[1::2]
用于将每个文本项与相应的删除字符和最后一项(在删除的字符中不匹配))与itertools.izip_longest
组合,即fillvalue
。然后,使用''
连接这些元组中的每一个。请注意,这需要导入"".join(x)
(当然,您可以在一个简单的循环中执行此操作,但itertools
为这些事情提供了非常干净的解决方案)。另请注意,{3}中的itertools
称为itertools.izip_longest
。
这导致正则表达式的进一步简化,因为可以用简单的匹配组(itertools.zip_longest
而不是(some|interesting)
)代替前瞻,而不是使用辅助字符:
(.)(?=some|interesting)
此处,the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]
上的切片索引已交换,因为现在必须将偶数项目添加到项目中而不是在前面。另请注意the_raw_split
部分,这是将第一项与[""] +
配对以修复订单所必需的。
(编辑结束)
或者,您可以(如果需要)为每个拆分器使用""
而不是string.replace
(我认为这是您的首选项,但通常情况下可能更有效)
re.sub
此外,如果您使用固定令牌指示拆分的位置,则不需要for s in splitters:
mystr = mystr.replace(s, "!!" + s)
,但可以使用re.split
代替:
string.split
您还可以做什么(而不是依赖替换令牌不在其他任何地方的字符串中或依赖于每个分割位置前面有非单词字符)是使用{{在输入中查找拆分字符串1}}并使用字符串切片来提取片段:
result = mystr.split("!!")
在这里,string.find
生成一个可以找到分割符的位置列表,对于字符串中的所有分割符(为此,def split(string, splitters):
while True:
# Get the positions to split at for all splitters still in the string
# that are not at the very front of the string
split_positions = [i for i in (string.find(s) for s in splitters) if i > 0]
if len(split_positions) > 0:
# There is still somewhere to split
next_split = min(split_positions)
yield string[:next_split] # Yield everything before that position
string = string[next_split:] # Retain the rest of the string
else:
yield string # Yield the rest of the string
break # Done.
被排除)并且不在开头(在此处)我们(可能)只是拆分,所以[i for i in (string.find(s) for s in splitters) if i > 0]
也被排除在外。如果字符串中有任何剩余,我们产生(这是一个生成器函数)一切直到(不包括)第一个拆分器(在i < 0
)并用剩余部分替换该字符串。如果没有,我们产生字符串的最后一部分并退出该函数。因为它使用i == 0
,所以它是一个生成器函数,因此您需要使用min(split_positions)
将其转换为实际列表。
请注意,您也可以通过调用yield
替换list
(如果您之前已定义yield whatever
)并在最后返回some_list.append
,我不认为但是,这是非常好的代码风格。
如果您对使用正则表达式没问题,请使用
some_list
否则,使用some_list
并使用以下分割函数也可以实现相同的目的:
the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]
答案 1 :(得分:2)
不是特别优雅,但避免使用正则表达式:
mystr = "just some stupid string to illustrate my question"
splitters = ["some", "illustrate"]
indexes = [0] + [mystr.index(s) for s in splitters] + [len(mystr)]
indexes = sorted(list(set(indexes)))
print [mystr[i:j] for i, j in zip(indexes[:-1], indexes[1:])]
# ['just ', 'some stupid string to ', 'illustrate my question']
我应该在此承认,如果splitters
中的单词出现不止一次,则需要多做一些工作,因为str.index
只找到第一次出现的单词的位置...