【问题标题】:Invalid pattern in look-behind后视模式无效
【发布时间】:2019-11-27 21:16:32
【问题描述】:

为什么这个正则表达式在 Python 中有效,而在 Ruby 中无效:

/(?<!([0-1\b][0-9]|[2][0-3]))/

很高兴听到解释以及如何在 Ruby 中解决它

用整行代码编辑:

re.sub(r'(?<!([0-1\b][0-9]|[2][0-3])):(?!([0-5][0-9])((?i)(am)|(pm)|(a\.m)|(p\.m)|(a\.m\.)|(p\.m\.))?\b)' , ':\n' , s)

基本上,当有冒号并且不是时间时,我会尝试添加'\n'

【问题讨论】:

标签: python regex ruby regex-lookarounds


【解决方案1】:

Ruby 正则表达式引擎不允许在后视中捕获组。 如果需要分组,可以使用非捕获组(?:)

[8] pry(main)> /(?<!(:?[0-1\b][0-9]|[2][0-3]))/
SyntaxError: (eval):2: invalid pattern in look-behind: /(?<!(:?[0-1\b][0-9]|[2][0-3]))/
[8] pry(main)> /(?<!(?:[0-1\b][0-9]|[2][0-3]))/
=> /(?<!(?:[0-1\b][0-9]|[2][0-3]))/

Docs:

 (?<!subexp)        negative look-behind

                     Subexp of look-behind must be fixed-width.
                     But top-level alternatives can be of various lengths.
                     ex. (?<=a|bc) is OK. (?<=aaa(?:b|cd)) is not allowed.

                     In negative look-behind, capturing group isn't allowed,
                     but non-capturing group (?:) is allowed.

this answer学习。

【讨论】:

  • 我看到捕获组导致后向错误,但是当我将其更改为非捕获组时,它会超时。
  • 应该(?&lt;=aaa(?:b|cd))(?&lt;=aaa(b|cd)) 这是不允许的吗?
  • 超时的字符串是什么?也许有一些过度的回溯
  • 它在(?&lt;!(?:[0-1][0-9]|[2][0-3])) 上超时,这并没有太多可以回溯的地方。链接regexplanet.com/share/index.html?share=yyyyda5tvar
  • 你与这个正则表达式匹配的字符串是什么?
【解决方案2】:

累加。到Onigmo regex documentation在否定后视中不支持捕获组。虽然这在正则表达式引擎中很常见,但并非所有引擎都将其视为错误,因此您会看到 re 和 Onigmo 正则表达式库中的差异。

现在,至于您的正则表达式,它在 Ruby 和 Python 中都无法正常工作:PythonRuby 正则表达式中的字符类中的 \b 匹配 BACKSPACE (\x08) 字符,不是单词边界。此外,当您在可选的非单词 char 之后使用单词边界时,如果 char 出现在字符串中,则单词 char 必须立即出现在该非单词 char 的右侧。单词边界必须移动到m 之后的右侧\.? 之前。

当前方法的另一个缺陷是,lookbehinds 并不是最好的排除某些上下文的方法,比如这里。例如。你不能考虑时间数字和am/pm之间的可变数量的空格。最好匹配您不想触摸的上下文并匹配并捕获您要修改的上下文。所以,我们这里需要两个主要的替代方案,一个在时间字符串中匹配am/pm,另一个在所有其他上下文中匹配它们。

您的模式也有太多可以使用字符类和? 量词合并的替代方案。

Regex demo

  • \b((?:[01]?[0-9]|2[0-3]):[0-5][0-9]\s*[pa]\.?m\b\.?):
    • \b - 字边界
    • ((?:[01]?[0-9]|2[0-3]):[0-5][0-9]\s*[pa]\.?m\b\.?) - 捕获组 1:
      • (?:[01]?[0-9]|2[0-3]) - 一个可选的01 然后是任何数字或2 然后是从03 的数字
      • :[0-5][0-9] - : 然后是从 0059 的数字
      • \s* - 0+ 个空格
      • [pa]\.?m\b\.? - ap,可选点,m单词边界,可选点
  • | - 或
  • \b[ap]\.?m\b\.? - 单词边界,ap,可选点,m单词边界,可选点

Python fixed solution:

import re
text = 'am pm  P.M.  10:56pm 10:43 a.m.'
rx = r'\b((?:[01]?[0-9]|2[0-3]):[0-5][0-9]\s*[pa]\.?m\b\.?)|\b[ap]\.?m\b\.?'
result = re.sub(rx, lambda x: x.group(1) if x.group(1) else "\n", text, flags=re.I)

Ruby solution:

text = 'am pm  P.M.  10:56pm 10:43 a.m.'
rx = /\b((?:[01]?[0-9]|2[0-3]):[0-5][0-9]\s*[pa]\.?m\b\.?)|\b[ap]\.?m\b\.?/i
result = text.gsub(rx) { $1 || "\n" }

输出:

"\n \n  \n  10:56pm 10:43 a.m."

【讨论】:

    【解决方案3】:

    肯定@mrzasa 找到了问题所在。

    但是.. 猜测您是否打算用 ':\n`
    替换非时间冒号 我猜可以这样做。也做了一点空白修剪。

    (?i)(?<!\b[01][0-9])(?<!\b[2][0-3])([^\S\r\n]*:)[^\S\r\n]*(?![0-5][0-9](?:[ap]\.?m\b\.?)?)
    

    PCRE - https://regex101.com/r/7TxbAJ/1 替换 $1\n

    Python - https://regex101.com/r/w0oqdZ/1 替换 \1\n

    可读版本

     (?i)
     (?<!
          \b [01] [0-9] 
     )
     (?<!
          \b [2] [0-3] 
     )
     (                             # (1 start)
          [^\S\r\n]* 
          :
     )                             # (1 end)
     [^\S\r\n]* 
     (?!
          [0-5] [0-9] 
          (?: [ap] \.? m \b \.? )?
     )
    

    【讨论】:

      猜你喜欢
      • 2021-04-02
      • 2021-08-11
      • 2014-07-09
      • 2023-03-11
      • 2019-01-26
      • 1970-01-01
      • 1970-01-01
      • 2015-04-19
      • 1970-01-01
      相关资源
      最近更新 更多