【问题标题】:Regex match characters when not preceded by a string当前面没有字符串时,正则表达式匹配字符
【发布时间】:2020-01-18 21:09:52
【问题描述】:

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

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

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

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

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

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

我决定先尝试修复No.,使用最后两个条件。但它依赖于独立匹配No,我认为这会在其他地方出现误报。我不知道如何让它只在句号后面生成字符串No。然后,我将对Sgt. 和我遇到的任何其他“问题”字符串使用类似的方法。

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

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

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

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

【问题讨论】:

  • 您是否尝试过嵌套一些环视?例如(?&lt;=(?&lt;!No)[\.\?\!]) (有一个尾随空格)匹配标点符号前面的 1 个空格,只要后者前面没有 No。但是我不知道 Python 的正则表达式,所以我不知道如何克服 lookbehind 断言必须是固定宽度的限制,例如,这会导致 (?&lt;=(?&lt;!(No|Sgt))[\.\?\!]) 错误。跨度>
  • 这可能有很多边缘情况,但是对于您的示例数据,您可以尝试(?&lt;=[.?!](?! [a-z0-9]))(?&lt;!-Sgt\.) 请参阅regex101.com/r/RMn6dY/1 或者如果您可以使用正则表达式 PyPi 模块,您可以使用(?&lt;=[.?!](?! [a-z0-9]))(?&lt;![^\w\s]\S*\.) @ 987654323@
  • @Thefourthbird,理论上()[] 应该也足以成为处理边缘情况的工具。但是生成的正则表达式可能确实很长不可读(就像您尝试在数字区间中匹配数字时一样)。
  • 你能把潜在的边缘情况缩小到一个固定的集合吗?因为如果不是,我怀疑您是否能够在没有误报或误报的情况下做到这一点。让我们接受“不”。例如。是的,在您给出的上下文中,指的是“数字”的简短形式,而不是句子的结尾。但是,“不。我是你父亲”或“我的答案是否定的”呢?没有上下文就不可能区分这些。对于“否”,仅检查后续数字可能就足够了,但对于其他数字可能不会。例如:“军士头衔通常缩写为 Sgt。”
  • 当您可以使用 2 个简单的正则表达式([\.\?!](?:[A-Z]\.){2,}|No\.) 一个循环和一个条件...

标签: regex python-3.x regex-lookarounds


【解决方案1】:

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

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

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

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

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

对于 OP 和普通读者,this question and the answers to it 是关于环视的,非常有趣。

【讨论】:

    【解决方案2】:

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

    使用类似的模式

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

    regex demo

    它与我发布的here 非常相似,但它包含一个匹配格式不佳的浮点数的模式,添加了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 $6. 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 $6. 00 because you bought me a No. 3 burger.
    -Sgt. Smith
    

    图案详情

    • \s* - 匹配 0 个或多个空格(用于修剪结果)
    • (?:\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]) - 匹配一个点,后面没有 1 个或多个空格,然后是可选的 - 和大写字母或数字
      • | - 或
      • [^.!?] - 除.!? 之外的任何字符
    • (?:[.?!]|$) - .!? 或字符串结尾。

    【讨论】:

      【解决方案3】:

      正如我在上面的评论中提到的,如果您无法定义一组固定的边缘情况,那么如果没有误报或漏报,这可能是不可能的。同样,在没有上下文的情况下,您无法区分“-Sgt. Smith”之类的缩写和“Sergeant 通常缩写为 Sgt。这使其更短”之类的句子结尾。

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


      1.确定您的边缘案例

      例如,您可以通过检查后续数字来区分“我有 3 号”和“不。我是你父亲”。所以你会用这样的正则表达式来识别边缘情况:No. \d。 (同样,上下文很重要。像“200 够吗?不。200 还不够。”这样的句子仍然会给你一个误报)

      2。掩盖你的边缘情况

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

      3.运行你的算法

      现在您已经摆脱了不需要的标点符号,您可以像这样运行一个更简单的正则表达式来识别真正的阳性:[\.\!\?]\s

      4.揭露你的边缘情况

      将“======NUMBER======”转回“否”。

      【讨论】:

        【解决方案4】:

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

        我自己会通过三个步骤来完成:

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

        例如:

        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 $6. 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 $6. 00 because you bought me a No. 3 burger.',
         '-Sgt. Smith']
        

        【讨论】:

        • 感谢大家提供非常详细的答案,我选择了这个,因为它非常简单。我知道几乎不可能涵盖所有可能的边缘情况——我只是在寻找一种方法来纠正其中的大多数情况。然而,我确实将第一个表达式调整为(?&lt;=[\dA-Z]\.)\s+|(?&lt;=No\.)\s+,因为我可以合理地确定“正常”句子不会以大写字母结尾。我在 python 中对其进行参数化以使用“异常”字符串列表代替No,例如Jan.Feb.Col. 等。可能不是最有效的,但它适用于我的小数据跨度>
        猜你喜欢
        • 2021-10-22
        • 1970-01-01
        • 1970-01-01
        • 2018-06-12
        • 1970-01-01
        • 1970-01-01
        • 2015-06-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多