寻找与我当前函数类似结果的正则表达式模式
我有一些用帕斯卡命名法(Pascal Case)写的文本,想把它们分成单独的词或标记。比如,"Hello123AIIsCool"
应该变成 ["Hello", "123", "AI", "Is", "Cool"]
。
一些条件
- 每个“词”总是以大写字母开头。例如,
"Hello"
- 连续的数字应该保持在一起。例如,
"123"
应该变成["123"]
,而不是["1", "2", "3"]
- 连续的大写字母也应该保持在一起,但如果最后一个字母是新词的开头,就要分开。比如,
"ABCat"
应该变成["AB", "Cat"]
,而不是["ABC", "at"]
- 并不能保证每个条件在字符串中都有匹配的部分。比如,
"Hello"
、"HelloAI"
、"HelloAIIsCool"
、"Hello123"
、"123AI"
、"AIIsCool"
,以及其他我没有提供的组合都是可能的候选者。
我尝试了几种正则表达式的变体。以下两个尝试让我离想要的结果很近,但还不够。
版本 0
import re
def extract_v0(string: str) -> list[str]:
word_pattern = r"[A-Z][a-z]*"
num_pattern = r"\d+"
pattern = f"{word_pattern}|{num_pattern}"
extracts: list[str] = re.findall(
pattern=pattern, string=string
)
return extracts
string = "Hello123AIIsCool"
extract_v0(string)
['Hello', '123', 'A', 'I', 'Is', 'Cool']
版本 1
import re
def extract_v1(string: str) -> list[str]:
word_pattern = r"[A-Z][a-z]+"
num_pattern = r"\d+"
upper_pattern = r"[A-Z][^a-z]*"
pattern = f"{word_pattern}|{num_pattern}|{upper_pattern}"
extracts: list[str] = re.findall(
pattern=pattern, string=string
)
return extracts
string = "Hello123AIIsCool"
extract_v1(string)
['Hello', '123', 'AII', 'Cool']
目前最佳选择
这个方法结合了正则表达式和循环。它能工作,但这是最好的解决方案吗?还是有更高级的正则表达式可以做到这一点?
import re
def extract_v2(string: str) -> list[str]:
word_pattern = r"[A-Z][a-z]+"
num_pattern = r"\d+"
upper_pattern = r"[A-Z][A-Z]*"
groups = []
for pattern in [word_pattern, num_pattern, upper_pattern]:
while string.strip():
group = re.search(pattern=pattern, string=string)
if group is not None:
groups.append(group)
string = string[:group.start()] + " " + string[group.end():]
else:
break
ordered = sorted(groups, key=lambda g: g.start())
return [grp.group() for grp in ordered]
string = "Hello123AIIsCool"
extract_v2(string)
['Hello', '123', 'AI', 'Is', 'Cool']
5 个回答
2
你可以试试这个正则表达式:
[A-Z](?:[a-z]+|[A-Z]+(?![a-z]))?|\d+
查看这个 测试案例
import re
pattern = r"[A-Z](?:[a-z]+|[A-Z]+(?![a-z]))?|\d+"
text = "Hello123AIIsCoolAndHTML5IsAMarkupLanguage"
print(re.findall(pattern, text))
# ['Hello', '123', 'AI', 'Is', 'Cool', 'And', 'HTML', '5', 'Is', 'A', 'Markup', 'Language']
2
使用 re.sub
和 split()
方法
import re
def pascal_case_split(identifier):
return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', re.sub('([0-9]+)', r' \1', identifier))).split()
a = pascal_case_split("Hello123AIIsCool")
a
['Hello', '123', 'AI', 'Is', 'Cool']
3
根据你的版本1:
import re
def extract_v1(string: str) -> list[str]:
word_pattern = r"[A-Z][a-z]+"
num_pattern = r"\d+"
upper_pattern = r"[A-Z]+(?![a-z])" # Fixed
pattern = f"{word_pattern}|{num_pattern}|{upper_pattern}"
extracts: list[str] = re.findall(
pattern=pattern, string=string
)
return extracts
string = "Hello123AIIsCool"
extract_v1(string)
结果:
['Hello', '123', 'AI', 'Is', 'Cool']
这个固定的 upper_pattern
会尽可能多地匹配大写字母,如果遇到小写字母,它会在小写字母前停下来。