正则表达式匹配前面没有字符串的字符

Regex match characters when not preceded by a string

我正在尝试匹配标点符号后的空格,以便我可以拆分大量文本,但我看到一些常见的边缘情况,包括地点、标题和常见缩写:

I am from New York, N.Y. and I would like to say hello! How are you today? I am well. I owe you . 00 because you bought me a No. 3 burger. -Sgt. Smith

我将它与 Python 中的 re.split 函数一起使用 3 我想得到这个:

["I am from New York, N.Y. and I would like to say hello!",
"How are you today?",
"I am well.",
"I owe you . 00 because you bought me a No. 3 burger."
"-Sgt. Smith"]

这是我目前的正则表达式:

(?<=[\.\?\!])(?<=[^A-Z].)(?<=[^0-9].)(?<=[^N]..)(?<=[^o].)

我决定首先尝试修复 No.,最后两个条件。但它依赖于独立匹配 No,我认为这会在其他地方出现误报。我无法弄清楚如何让它只制作句点后面的字符串 No 。然后,我将对 Sgt. 和我遇到的任何其他 "problem" 字符串使用类似的方法。

我正在尝试使用类似的东西:

(?<=[\.\?\!])(?<=[^A-Z].)(?<=[^0-9].)^(?<=^No$)

但在那之后它没有捕捉到任何东西。我怎样才能让它排除某些我希望其中有句点的字符串,而不是捕获它们?

这是我的情况的正则表达式:https://regexr.com/4sgcb

只使用一个正则表达式会很棘手 - 正如评论中所述,有很多边缘情况。

我自己会分三步完成:

  1. 将应该保留的 space 替换为一些特殊字符 (re.sub)
  2. 拆分文本 (re.split)
  3. 将特殊字符替换为space

例如:

import re

zero_width_space = '\u200B'

s = 'I am from New York, N.Y. and I would like to say hello! How are you today? I am well. I owe you . 00 because you bought me a No. 3 burger. -Sgt. Smith'

s = re.sub(r'(?<=\.)\s+(?=[\da-z])|(?<=,)\s+|(?<=Sgt\.)\s+', zero_width_space, s)
s = re.split(r'(?<=[.?!])\s+', s)

from pprint import pprint
pprint([line.replace(zero_width_space, ' ') for line in s])

打印:

['I am from New York, N.Y. and I would like to say hello!',
 'How are you today?',
 'I am well.',
 'I owe you . 00 because you bought me a No. 3 burger.',
 '-Sgt. Smith']

这是我能得到的最接近的正则表达式(尾随 space 是我们匹配的那个):

(?<=(?<!(No|\.\w))[\.\?\!])(?! *\d+ *) 

它也会在 Sgt. 之后拆分,原因很简单,即后向断言必须在 Python 中固定宽度(限制!)。

这就是我在 vim 中的做法,它没有这样的限制(尾随 space 是我们匹配的那个):

\(\(No\|Sgt\|\.\w\)\@<![?.!]\)\( *\d\+ *\)\@!\zs 

对于 OP 以及休闲 reader, 都是环顾四周,非常有趣。

您可以考虑一种匹配方法,它可以让您更好地控制要计为单个词而不是断句信号的实体。

使用类似

的模式
\s*((?:\d+\.\s*\d+|(?:No|M[rs]|[JD]r|S(?:r|gt))\.|\.(?!\s+-?[A-Z0-9])|[^.!?])+(?:[.?!]|$))

regex demo

它与我发布的内容非常相似 ,但它包含一个匹配格式不正确的浮点数的模式,添加了 No.Sgt. 缩写支持以及更好地处理未以最后一句标点符号结尾的字符串。

Python demo:

import re
p = re.compile(r'\s*((?:\d+\.\s*\d+|(?:No|M[rs]|[JD]r|S(?:r|gt))\.|\.(?!\s+-?[A-Z0-9])|[^.!?])+(?:[.?!]|$))')
s = "I am from New York, N.Y. and I would like to say hello! How are you today? I am well. I owe you . 00 because you bought me a No. 3 burger. -Sgt. Smith"
for m in p.findall(s):
    print(m)

输出:

I am from New York, N.Y. and I would like to say hello!
How are you today?
I am well.
I owe you . 00 because you bought me a No. 3 burger.
-Sgt. Smith

图案详情

  • \s* - 匹配 0 个或多个空格(用于 trim 结果)
  • (?:\d+\.\s*\d+|(?:No|M[rs]|[JD]r|S(?:r|gt))\.|\.(?!\s+-?[A-Z0-9])|[^.!?])+ - 多个替代词出现一次或多次:
    • \d+\.\s*\d+ - 1+ 位,.,0+ 空格,1+ 位
    • (?:No|M[rs]|[JD]r|S(?:r|gt))\. - 缩写字符串,如 No.Mr.Ms.Jr.Dr.Sr.Sgt.
    • \.(?!\s+-?[A-Z0-9]) - 匹配后跟一个或多个空格的点,然后是可选的 - 和大写字母或数字
    • | - 或
    • [^.!?] - 除了 .!?
    • 之外的任何字符
  • (?:[.?!]|$) - .!? 或字符串结尾。

正如我在上面的评论中提到的,如果您无法定义一组固定的边缘情况,那么如果没有误报或漏报,这可能是不可能的。同样,如果没有上下文,您将无法区分“-Sgt. Smith”等缩写和 "Sergeant is often times abbreviated as Sgt. This makes it shorter.".

等句子结尾

但是,如果您可以 定义一组固定的边缘情况,则分多个步骤执行此操作可能更容易且可读性更高。


1.确定您的边缘案例

例如,您可以通过检查后续数字来区分 "Ill have a No. 3" 和 "No. I am your father"。所以你会用这样的正则表达式来识别边缘情况:No. \d。 (同样,上下文很重要。像 "Is 200 enough? No. 200 is not enough." 这样的句子仍然会给你一个误报)

2。掩盖你的边缘情况

对于每个边缘情况,用 100% 不属于原始文本的相应字符串屏蔽字符串。例如。 "No." => "======NUMBER======"

3。 运行你的算法

现在您已经删除了不需要的标点符号,您可以 运行 一个更简单的正则表达式来识别真正的积极因素:[\.\!\?]\s

4.揭开你的边缘案例

将“======NUMBER======”转回"No."