用 lookahead/lookbehind 和空字符串拆分字符串

Split string with lookahead/lookbehind with empty string

我试图在每个 0 或一组 0 之后拆分任何数字字符串,例如 3.1415926535897932384626433832795028841971。但是,我想在每组之后保留 0。

例如字符串10203040506070809011应该拆分成

['10', '20', '30', '40', '50', '60', '70', '80', '90', '11']

字符串 3.1415926535897932384626433832795028841971 应该拆分成

['3.14159265358979323846264338327950', '28841971']

我试图用正后视和空字符串拆分字符串:

import re
p = '(?<=0+)'

re.search(p, '102030405')
><_sre.SRE_Match object; span=(2, 2), match=''>

'102030405'.split(p)
>['102030405']

但这根本不会拆分字符串,即使模式匹配也是如此。

我也试过根据 0 拆分字符串并在前几个字符串后添加 0 ,但它看起来很复杂且效率低下。

l = '102030405'.split('0')
[e+'0' for e in l[:-1]] + [l[-1]]
>['10', '20', '30', '40', '5']

有没有什么方法可以根据空字符串的前瞻或后视来拆分字符串?我问的是一般情况,而不仅仅是数字。例如,如果我想在不丢失 ampm 的情况下将 3:18am5:19pm10:28am 拆分成不同的时间,并得到一个数组 ['3:18am', '5:19pm', '10:28am'],我将如何去做?

使用re.findall:

l = re.findall(r'(?<![^0])[1-9.]+0*', s)

关键是使用双重否定:前面没有不是零(匹配前面的零或字符串的开头)

Python split 需要非零宽度匹配。

您可以将 findall 与此正则表达式一起使用以获得您的匹配项:

>>> print re.findall(r'([\d.]+?(?:0+|$))', '10203040506070809011')
['10', '20', '30', '40', '50', '60', '70', '80', '90', '11']

>>> print re.findall(r'([\d.]+?(?:0+|$))', '3.1415926535897932384626433832795028841971')
['3.14159265358979323846264338327950', '28841971']

([\d.]+?(?:0|$)) 匹配以 0 或行尾结尾的数字或点。


更新:

但是我从您编辑的问题和评论中注意到您正在寻找一个通用解决方案来使用零宽度正则表达式模式进行拆分操作。

我建议你在 python 中安装非常有用的 regex module。此模块的版本 1 提供了大部分 PCRE 功能,并且远远超过默认 re 模块。

安装非常简单。只需从上面 link 下载 tar gzip 文件,然后 运行:

sudo python setup.py install

从解压 tar 文件后得到的目录中。 (忽略安装过程中的一些警告)。

安装 regex 后,只需使用此代码:

>>> import regex

>>> regex.DEFAULT_VERSION = regex.VERSION1

>>> regex.split(r'(?<=[ap]m)(?=.)', '3:18am5:19pm10:28am')
['3:18am', '5:19pm', '10:28am']

>>> print regex.split(r'(?<=0)(?=[1-9])', '10203040506070809011')
['10', '20', '30', '40', '50', '60', '70', '80', '90', '11']

>>> print regex.split(r'(?<=0)(?=[1-9])', '3.1415926535897932384626433832795028841971')
['3.14159265358979323846264338327950', '28841971']

>>> print regex.split(r'(?<=0)(?=[1-9])', '10020')
['100', '20']

re.findall 中的这个简单正则表达式应该足够了:

l = re.findall(r'[.1-9]+(?:0+|$)', s)

注:

  • findall returns 字符串中模式的所有非重叠匹配项作为列表 个字符串。

  • 对于每个匹配项,我们需要最长的数字串(或点)以至少一个零结尾,或字符串结尾

  • 末尾的零不应被捕获为另一个匹配项(因此 (?:...

与你第二个例子类似:

>>> re.findall(r'[\d:]+(?:am|pm|$)', '3:18am5:19pm10:28am')
['3:18am', '5:19pm', '10:28am']

无需 lookahead/lookbehind 魔术或非贪婪匹配。

Anubhava 的回答是正确的。但是需要安装regex模块,不需要。

import re
pattern = r"(?<=0)(?=[1-9])"
s = "3.1415926535897932384626433832795028841971"
s2 = "10203040506070809011"
re.split(pattern, s)
# ['3.14159265358979323846264338327950', '28841971']
re.split(pattern, s2)
# ['10', '20', '30', '40', '50', '60', '70', '80', '90', '11']

您应该查看 re 模块页面以了解有关先行和后行的更多详细信息。 如果我要解释的话。我会说

(?<=...) 表示分隔符之前的内容。

(?=...) 表示分隔符后的内容。

因此,(?<=0)(?=[1-9]) 表示分隔符之前是空的,应该有一个零,而在它之后是 1 到 9。

regex 和 re. 的速度比较

expression time
re.split(r"(?<=0)(?=[1-9])", s) 5.78 ns ± 0.103 ns
regex.split(r"(?<=0)(?=[1-9])", s) 6.04 ns ± 0.364 ns
re.split(r"(?<=0)(?=[1-9])", s2) 5.83 ns ± 0.061 ns
regex.split(r"(?<=0)(?=[1-9])", s2) 6.34 ns ± 1.16 ns