Python正则表达式的正向先行断言无法正确分割。

huangapple go评论78阅读模式
英文:

Python regex positive lookahead cannot split correctly

问题

[^a-z]+ behaves like a lazy match because it's using the + quantifier after a negated character class. This means it will match the shortest possible sequence of characters that do not fall within the range of lowercase letters (a to z). To make it behave as a greedy match, you can use [^a-z]+ followed by + without the negated character class, like this: [^a-z]+.

Here's the corrected regular expression for splitting the text by sections:

re.split(r'\n(?=[A-Z\d])', text)

This regex uses a positive lookahead assertion to split the text at line breaks (\n) that are followed by an uppercase letter or a digit, which should correctly split the text into sections as you expected.

英文:

I've text consisting of sections. In each section:

  • The title is in uppercase and may span multiple lines
  • The body may have acronyms, so we cannot assume that uppercase words mark the start of each section

There may be zero or multiple line breaks between sections.

Example

import re

text = """
Lorem ipsum

THIS SECTION IS A SHORT STORY
1 Hello world
2 Bye bye
Side comment


NEXT SECTION SPANS 200
YEARS AND MANY COUNTRIES!

3 Joe Bloggs attended a NATO summit
4 John Doe heard...
THIS SECTION HAS NO
LINE BREAK / SPACE FROM
THE PREVIOUS ONE

5 Alice thought...
6 Bob visited...
""".strip()

re.split("\n(?=[^a-z]+\n+[a-z\d])", text)

I expected it to split the text by sections like this:

["Lorem ipsum\n",
 "THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\nSide comment\n\n",
 "NEXT SECTION SPANS 200\nYEARS AND MANY COUNTRIES!\n\n3 Joe Bloggs attended a NATO summit\n4 John Doe heard...",
 "THIS SECTION HAS NO\nLINE BREAK / SPACE FROM\nTHE PREVIOUS ONE\n\n5 Alice thought...\n6 Bob visited..."]

Instead, Python splits up each section as follows, which seems to contradict the lookahead assertion:

["Lorem ipsum",
 "",
 "THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\nSide comment",
 "",
 "",
 "NEXT SECTION SPANS 200",
 "YEARS AND MANY COUNTRIES!\n\n3 Joe Bloggs attended a NATO summit\n4 John Doe heard...",
 "THIS SECTION HAS NO",
 "LINE BREAK / SPACE FROM",
 "THE PREVIOUS ONE\n\n5 Alice thought...\n6 Bob visited..."]

Questions

Why does [^a-z]+ behave like a lazy match instead of greedy match?

What's the correct solution?

答案1

得分: 1

更新的示例

我们可以添加一个回顾来匹配双\n(或者在不需要尾随\n的情况下拆分\n\n),并在字符集中包含数字。

re.split(r"(?<=\n)\n(?=[A-Z0-9 ]+\n)", text)

或者 (?<=\n)\n(?= *[A-Z][A-Z0-9 ]*\n) 来强制至少有一个初始大写字母。

输出:

['Lorem ipsum\n',
 'THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\n',
 'THIS SECTION SPANS 200\nYEARS AND MANY COUNTRIES\n3 Joe Bloggs saw...\n4 John Doe heard...\n',
 'THIS SECTION IS ALSO A\nLONG STORY ABOUT EVERYTHING\nSINCE 1669\n\n5 Alice thought...\n6 Bob visited...']

正则表达式演示

使用循环

import re

out = ['']
prev_header = True
for line in text.splitlines():
    if line:
        header = bool(re.fullmatch('[^a-z]+', line))
        if header and not prev_header:
            out.append(line+'\n')
        else:
            out[-1] += line+'\n'
        prev_header = header

输出:

['Lorem ipsum\n',
 'THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\nSide comment\n',
 'NEXT SECTION SPANS 200\nYEARS AND MANY COUNTRIES!\n3 Joe Bloggs attended a NATO summit\n4 John Doe heard...\n',
 'THIS SECTION HAS NO\nLINE BREAK / SPACE FROM\nTHE PREVIOUS ONE\n5 Alice thought...\n6 Bob visited...\n']
英文:

updated example

We can add a lookbehind to match a double \n (or split on \n\n if you don't need the trailing \n), and include digits in the set of characters.

re.split(r&quot;(?&lt;=\n)\n(?=[A-Z0-9 ]+\n)&quot;, text)

Or (?&lt;=\n)\n(?= *[A-Z][A-Z0-9 ]*\n) to force at least one initial uppercase.

Output:

[&#39;Lorem ipsum\n&#39;,
 &#39;THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\n&#39;,
 &#39;THIS SECTION SPANS 200\nYEARS AND MANY COUNTRIES\n3 Joe Bloggs saw...\n4 John Doe heard...\n&#39;,
 &#39;THIS SECTION IS ALSO A\nLONG STORY ABOUT EVERYTHING\nSINCE 1669\n\n5 Alice thought...\n6 Bob visited...&#39;]

regex demo

using a loop

import re

out = [&#39;&#39;]
prev_header = True
for line in text.splitlines():
    if line:
        header = bool(re.fullmatch(&#39;[^a-z]+&#39;, line))
        if header and not prev_header:
            out.append(line+&#39;\n&#39;)
        else:
            out[-1] += line+&#39;\n&#39;
        prev_header = header

Output:

[&#39;Lorem ipsum\n&#39;,
 &#39;THIS SECTION IS A SHORT STORY\n1 Hello world\n2 Bye bye\nSide comment\n&#39;,
 &#39;NEXT SECTION SPANS 200\nYEARS AND MANY COUNTRIES!\n3 Joe Bloggs attended a NATO summit\n4 John Doe heard...\n&#39;,
 &#39;THIS SECTION HAS NO\nLINE BREAK / SPACE FROM\nTHE PREVIOUS ONE\n5 Alice thought...\n6 Bob visited...\n&#39;]

huangapple
  • 本文由 发表于 2023年5月10日 20:29:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76218449.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定