正则表达式在字符串开头未匹配

时间:2019-05-29 19:45:21

标签: python regex string lookahead

我有as和bs的字符串。我想提取所有重叠的子序列,其中子序列是单个b包围的b。这是我写的正则表达式:

import re

pattern = """(?=            # inside lookahead for overlapping results
             (?:a|^)        # match at beginning of str or after a
             (b* (?:a) b*)  # one a between any number of bs
             (?:a|$))       # at end of str or before next a
          """
a_between_bs = re.compile(pattern, re.VERBOSE)

除了字符串中的第一个字符是a之外,这似乎可以按预期工作,在这种情况下,该子序列会丢失:

a_between_bs.findall("bbabbba")
# ['bbabbb', 'bbba']
a_between_bs.findall("abbabb")
# ['bbabb']

我不知道发生了什么。如果我更改潜在匹配的开始顺序,结果也会更改:

pattern = """(?=
             (?:^|a)        # a and ^ swapped
             (b* (?:a) b*)
             (?:a|$))
          """
a_between_bs = re.compile(pattern, re.VERBOSE)

a_between_bs.findall("abbabb")
# ['abb']

我希望这是对称的,因此也可能会丢失以a结尾的字符串,但事实并非如此。发生了什么事?

修改

我认为上述玩具示例的解决方案将转化为我的全部问题,但事实并非如此,因此,我正在详细说明(对此感到抱歉)。我正在尝试从转录的单词中提取“音节”。 “音节”是元音或重音,在其前面和之后是任意数量的辅音。这是提取它们的正则表达式:

vowels = 'æɑəɛiɪɔuʊʌ'
diphtongues = "|".join(('aj', 'aw', 'ej', 'oj', 'ow'))
consonants = 'θwlmvhpɡŋszbkʃɹdnʒjtðf'

pattern = f"""(?=
          (?:[{vowels}]|^|{diphtongues})
          ([{consonants}]* (?:[{vowels}]|{diphtongues}) [{consonants}]*)
          (?:[{vowels}]|$|{diphtongues})
          )
          """
syllables = re.compile(pattern, re.VERBOSE)

棘手的一点是,双音词以辅音(j或w)结尾,我不想将其包含在下一个音节中。因此,用双重否定(?<![{consonants}])代替第一个非捕获组是行不通的。我尝试改为用正向(?<=[{vowels}]|^|{diphtongues})代替该组,但是regex不会接受不同的长度(即使删除双音不起作用,显然^的长度也不同)。

所以这是上面模式中的问题情况:

syllables.findall('æbə')
# ['bə'] 
# should be: ['æb', 'bə']

修改2: 我已改用正则表达式,它允许进行可变宽度的回溯,从而解决了该问题。令我惊讶的是,它甚至似乎比标准库中的re模块更快。不过,我仍然想知道如何使用re模块。 (:

2 个答案:

答案 0 :(得分:1)

我建议通过两次否定来解决此问题:

(?=         # inside lookahead for overlapping results
 (?<![^a])  # match at beginning of str or after a
 (b*ab*)    # one a between any number of bs
 (?![^a])   # at end of str or before next a
)

请参见regex demo

请注意,我将 grouping 构造替换为 lookarounds (?:a|^)替换为(?<![^a])(?:a|$)替换为(?![^a])。后者并不是很重要,但是第一个在这里非常重要。

外部先行模式开头的(?:a|^)匹配a或字符串的开头,无论哪个先出现。如果a开头,则匹配它,当输入为abbabb时,您会得到bbabb,因为它与捕获组模式匹配并且紧随其后的是字符串位置的结尾。下一个迭代在第一个a之后开始,并且找不到任何匹配项,因为字符串中仅剩下的aa之后没有b

请注意,order of alternative matters。如果更改为(?:^|a),则匹配从字符串的开头开始,b*匹配空字符串,ab*抓取abb中的第一个abbabb,然后由于紧随其后的是a,因此您将获得abb作为匹配项。在第一个a之后无法匹配任何内容。

答案 1 :(得分:0)

请记住,python“短路”,因此,如果它匹配“ ^”,它就不会继续寻找它是否也匹配“ a”。这将“消耗”匹配的字符,因此,在匹配“ a”的情况下,将消耗“ a”,并且下一个匹配的组将不可用“ a”,并且因为使用(?:)语法不能捕获“ “ a”是“丢失”的,不能被下一个分组(b *(?: a)b *)捕获,而当“ ^”被第一个分组占用时,第一个“ a”将与第二个分组。