【问题标题】:Word boundary with words starting or ending with special characters gives unexpected results以特殊字符开头或结尾的单词边界会产生意想不到的结果
【发布时间】:2022-12-05 23:46:14
【问题描述】:

假设我想匹配短语 test Sortes\index[persons]{Sortes} text 中出现的短语 Sortes\index[persons]{Sortes}

使用 python re 我可以这样做:

>>> search = re.escape('Sortes\index[persons]{Sortes}')
>>> match = 'test Sortes\index[persons]{Sortes} text'
>>> re.search(search, match)
<_sre.SRE_Match object; span=(5, 34), match='Sortes\\index[persons]{Sortes}'>

这可行,但我想避免使用搜索模式Sortes 对短语test Sortes\index[persons]{Sortes} text 给出肯定的结果。

>>> re.search(re.escape('Sortes'), match)
<_sre.SRE_Match object; span=(5, 11), match='Sortes'>

所以我使用 \b 模式,如下所示:

search = r'\b' + re.escape('Sortes\index[persons]{Sortes}') + r'\b'
match = 'test Sortes\index[persons]{Sortes} text'
re.search(search, match)

现在,我没有匹配。

如果搜索模式不包含任何字符 []{},它就可以工作。例如。:

>>> re.search(r'\b' + re.escape('Sortes\index') + r'\b', 'test Sortes\index test')
<_sre.SRE_Match object; span=(5, 17), match='Sortes\\index'>

另外,如果我删除最后的r'\b',它也可以工作:

re.search(r'\b' + re.escape('Sortes\index[persons]{Sortes}'), 'test Sortes\index[persons]{Sortes} test')
<_sre.SRE_Match object; span=(5, 34), match='Sortes\\index[persons]{Sortes}'>

此外,documentation 说的是 \b

请注意,形式上,\b 被定义为 \w 和 \W 字符之间的边界(反之亦然),或者 \w 和字符串的开头/结尾之间的边界。

所以我尝试用(\W|$)替换最后的\b

>>> re.search(r'\b' + re.escape('Sortes\index[persons]{Sortes}') + '(\W|$)', 'test Sortes\index[persons]{Sortes} test')
<_sre.SRE_Match object; span=(5, 35), match='Sortes\\index[persons]{Sortes} '>

瞧,它起作用了! 这里发生了什么?我错过了什么?

【问题讨论】:

  • },你模式的最后一个字符是一个非单词字符,后面的空格也是。因此没有单词边界,也没有匹配。如果最后一个字符是s,它是一个单词字符,因此存在单词边界。

标签: python regex


【解决方案1】:

查看单词边界匹配的内容:

单词边界可以出现在三个位置之一:

  • 在字符串的第一个字符之前,如果第一个字符是单词字符。
  • 在字符串的最后一个字符之后,如果最后一个字符是单词字符。
  • 字符串中两个字符之间,其中一个是单词字符,另一个不是单词字符。

在您的模式中,} 仅在} 之后有字符字符(字母、数字或_)时才匹配。

当您使用 (W|$) 时,您需要一个非单词或字符串结尾明确地.

一个解决方案是自适应词边界:

re.search(r'(?:(?!w)|(?=w)){}(?:(?<=w)|(?<!w))'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')

或同等学历:

re.search(r'(?!Bw){}(?<!wB)'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')

在这里,使用自适应动态词边界意味着以下内容:

  • (?:(?!w)|(?=w))(等于(?!Bw))- 左侧边界,如果下一个字符是单词 char,则确保当前位置位于单词边界,或者如果下一个字符不是单词,则不应用上下文限制字符 (笔记你需要使用 (?:B(?!w)|(?=w)) 如果你想在下一个字符不是单词 char 的情况下立即禁止左边的单词 char)
  • (?:(?&lt;=w)|(?&lt;!w))(等于(?&lt;!wB))- 右侧边界,如果前一个字符是单词 char,则确保当前位置位于单词边界,或者如果前一个字符不是单词,则不应用上下文限制字符 (笔记你需要使用(?:(?&lt;=w)|B(?&lt;!w))如果你想在前面的字符不是单词字符的情况下立即禁止右边的单词字符)。

您也可以考虑使用明确的单词边界基于这些情况下的负面环视:

re.search(r'(?<!w){}(?!w)'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')

这里,(?&lt;!w)negative lookbehind 如果紧邻当前位置左侧的单词 char 将匹配失败,(?!w)negative lookahead 如果紧邻当前位置右侧的单词 char 将匹配失败.

选择哪个?与明确的词边界相比,自适应词边界更宽松,因为后者假定匹配的两端必须没有词字符,而前者允许在任何上下文中匹配前导和尾随非词字符。

笔记:很容易进一步自定义这些环视模式(比如,只有在有字母在模式周围,使用 [^Wd_] 而不是 w,或者如果您只允许匹配空格,请使用空白边界(?&lt;!S) / (?!S) 环视边界)。

【讨论】:

  • 我喜欢关于负面环视的建议。这个正则表达式匹配在我的代码中非常热门,所以我担心匹配的性能。环顾四周会有问题吗?
  • @Stenskjaer 也是一个零宽度断言,就像任何其他环视一样。由于这些环视模式仅包含单个原子,因此开销与s 中已有的开销应该不会有太大差异。好吧,如果你担心的话,你可以设置一个快速的性能测试,但这是我能想到的解决问题的唯一正确的正则表达式方法。
  • 真的!我只是自己测试过。性能没有(可检测到的)差异。谢谢。
  • 自适应词边界可以这样写:(?:(?!w)|(?=w)) => (?!Bw)(?:(?&lt;=w)|(?&lt;!w)) => (?&lt;!wB)
  • 此外,我录制了一个 "Dynamic adaptive word boundaries" YT 视频,其中我详细解释了这些结构。
【解决方案2】:

我认为这就是您遇到的问题:

位于wW 的边界上,但在示例中不起作用。 '{Sortes}'WW 之间的边界,因为'}'[a-zA-Z0-9_] 不匹配,w 的普通集合。

【讨论】:

    猜你喜欢
    • 2021-02-05
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多