【问题标题】:Python Regex get Unique Multiline MatchesPython Regex 获得唯一的多行匹配
【发布时间】:2021-06-05 13:40:24
【问题描述】:

由于背景很难解释,我正在编写伪代码, 我只对 Python-Regex-Pattern 感兴趣,希望你们能帮助我

我有以下输入文本(很多行以\n 作为行分隔符压缩为'.'):

.
.
1 Order 
order1 stuff
order1 stuff
etc
ShippingMethod: Truck
.
.
2 Order
order2 stuff
order2 stuff
etc
ShippingMethod: Truck
.
.
Order Summary
.
.

我只想为每个订单单独匹配“订单”和“卡车”之间的文本,然后我会在程序中进一步迭代结果。

我的正则表达式:(为了更好的可读性,我将其拆分为“开始、内容、结束”)。

pattern = \d\s*Order + [.|\s|\S]* + Truck

当我执行这个匹配时,我得到一个结果,从 1 Order 开始并在 second Truck 停止:

1 Order 
order1 stuff
order1 stuff
etc
ShippingMethod: Truck
.
.
2 Order
order2 stuff
order2 stuff
etc
ShippingMethod: Truck

我想要(在这种情况下)正好两个只包含一个订单内容的匹配项:

1 Order 
order1 stuff
order1 stuff
etc
ShippingMethod: Truck
2 Order
order2 stuff
order2 stuff
etc
ShippingMethod: Truck

我希望很清楚我在寻找什么。非常感谢任何帮助。
在此先感谢,保持安全,保持健康!

您可能会提出的建议:

  • 由于输入文本是 PDF 文本提取器的结果,因此您必须在行首和单词之间假设不同数量的空格。但是 \n 是可以信任的。基本上是写 \n 写 \s*\n
  • 我不能使用“订单”作为模式的结尾部分,因为在最后一个订单之后,接下来就是一个摘要。
  • “ShippingMethod”在我的语言中是不同的,这就是我在此示例中使用“Truck”的原因。我会设法重写。

【问题讨论】:

    标签: python regex pattern-matching match multiline


    【解决方案1】:

    不使用re.DOTALL,如果Truck不存在,防止过度匹配,你可以使用:

    ^\d+\s*Order\b.*(?:\n(?!\d+\s* Order\b|.* Truck$).*)*\n.* Truck$
    

    模式匹配:

    • ^ 字符串开始
    • \d+\s*Order\b.* 匹配数字后跟 Order 和该行的其余部分
    • (?:非捕获组
      • \n(?!\d+\s* Order\b|.* Truck$) 匹配换行符并断言该行不以数字和Order 开头并断言该行不以Truck 结尾
      • .*如果断言为真,则匹配整行
    • )*关闭非捕获组以匹配所有行
    • \n.* Truck$ 匹配换行符和以Truck 结尾的行的其余部分

    Regex demo | Python demo

    import re
     
    regex = r"^\d+\s*Order\b.*(?:\n(?!\d+\s* Order\b|.* Truck$).*)*\n.* Truck$"
     
    s = ("\n\n"
        "1 Order \n"
        "order1 stuff\n"
        "order1 stuff\n"
        "etc\n"
        "ShippingMethod: Truck\n\n\n"
        "2 Order\n"
        "order2 stuff\n"
        "order2 stuff\n"
        "etc\n"
        "ShippingMethod: Truck\n\n\n"
        "Order Summary\n\n")
     
    print(re.findall(regex, s, re.MULTILINE))
    

    输出

    ['1 Order \norder1 stuff\norder1 stuff\netc\nShippingMethod: Truck', '2 Order\norder2 stuff\norder2 stuff\netc\nShippingMethod: Truck']
    

    【讨论】:

    • 也谢谢你,我希望我没有占用你太多时间,因为它已经按预期工作了。我的意思是我会使用 re.DOTALL,但我想将最大行为控制放入 CSV,而不是在代码中。我可能会在 CSV 中添加另一个 True/False 列,并在代码中执行 if/else...
    【解决方案2】:

    解决方案看似简单——使用非贪婪运算符?

    首先,字符类正则表达式[] 匹配其中的任何字符,因此要匹配ab,正则表达式是[ab] 而不是[a|b]。所以代码的 content 部分应该是 [.\s\S]
    另外,\s\S 分别匹配所有空格和非空格,所以句点 (.) 是这里无关紧要。

    所以最终的内容部分应该是这样的:[\s\S]*

    现在是实际解决方案:

    +*? 等任何正常频率运算符之后的贪婪? 运算符告诉正则表达式匹配尽可能少的元素。使用*,您使用的是默认的零或更多 贪心版本,告诉正则表达式尽可能多地匹配(最终匹配您想要的第一个Truck! )

    所以我们在最后添加一个非贪心运算符,所以最终的正则表达式如下所示:

    \d\s*Order[\s\S]*?Truck
    

    奖励建议:

    字符类[\s\S] 是告诉正则表达式匹配每个字符的一种巧妙方法(因为每个字符要么是空格,要么不是空格)。但事实证明,有一种方法可以通过使用re.DOTALL 修饰符来提高效率。它按照它所说的去做 - 它告诉正则表达式 .(点)应该匹配所有字符,包括换行符。

    如果这是您使用的代码:

    re.findall(r'\d\s*Order[\s\S]*?Truck', input_text)
    

    这是最好的代码(包括问题的解决方案):

    re.findall(r'\d\s*Order.*?Truck', input_text, re.DOTALL)
    

    如您所见,.*? 现在将匹配从OrderTruck 的所有内容(包括换行符)。

    【讨论】:

    • 它只是工作。太感谢了!不幸的是,我无法使用 re.DOTALL 技巧,因为我的正则表达式的三个部分是在外部 CSV 文件中定义的。因此,对行为进行硬编码可能适用于我想要提取的这些信息,但对于下一条规则可能会过度。
    • imgur.com/a/jXPEQvp - 我使用 removeprefix 和 removesuffix 来(不)将这些部分包含到我的结果中(0/1)。还有五列 - 这是你贡献的一个美丽的怪物哈哈。
    • 啊,不错,如果您使用的是 CSV,那么不使用 DOTALL 绝对是明智之举。
    • 制作美丽的怪物是编码lmao的精髓
    猜你喜欢
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多