【问题标题】:How to extract specific strings using Python Regex如何使用 Python Regex 提取特定字符串
【发布时间】:2020-01-09 15:47:21
【问题描述】:

我一直在努力解决非常具有挑战性的字符串。
例如,

str1 = '95% for Pikachu, 92% for Sandshrew'
str2 = '70% for Paras & 100% Arcanine'
str3 = '99% Diglett, 40% Dugtrio'
str4 = '10% Squirtle, 100% for Alakazam'
str5 = '30% Metopod & 99% Dewgong'

字符串以%整数开头,可能有for,然后是口袋妖怪的名字。可能有comma(,)& 符号,然后是新的% 整数。终于有了pokemon的另一个名字。(全部以大写字母开头)
我要提取两个pokemon,比如result,

['Pikachu', 'Sandshrew']
['Paras', 'Arcanine']
['Diglett', 'Dugtrio']
['Squirtle', 'Alakazam']
['Metopod', 'Dewgong']

然后我可以使用in 语法创建所有 pokemen 的列表,但这不是最好的方法(以防他们添加更多 pokemon)。是否可以使用正则表达式进行提取?
提前致谢!
编辑
根据要求,我正在添加我的代码,

str_list = [str1, str2, str3, str4, str5]

for x in str_list:
    temp_list = []
    if 'for' in x:
        temp = x.split('% for', 1)[1].strip()
        temp_list.append(temp)
    else:
        temp = x.split(" ", 1)[1]
        temp_list.append(temp)
print(temp_list)

我知道这不是正则表达式。我试过的表达是,\d+ 提取整数开始...但不知道如何开始。
EDIT2
@b_c 有很好的边缘情况,所以我在这里添加它

edge_str = '100% for Pikachu, 29% Pika Pika Pikachu'

结果

['Pikachu', 'Pika Pika Pikachu']

【问题讨论】:

  • 请发布您尝试解决此问题的代码。
  • 您的正则表达式是否需要支持Mr. MimeMime Jr.Porygon2Type: Null? (其他不熟悉的口袋妖怪名称)

标签: python regex


【解决方案1】:

希望我没有过度设计这个,但我想涵盖稍微复杂的命名口袋妖怪的边缘情况,例如“Mime 先生”、“Farfetch'd”和/或“Nidoran♂” "(仅查看前 151 个)。

我使用的模式是(?:(?:\d+%(?: |for)+([A-Z](?:[\w\.♀♂']|(?: (?=[A-Z])))+))+)[, &]*,它看起来在我的测试中有效(这里是regex101 link 用于细分)。

对于一般摘要,我正在寻找:

  • 1+ 位后跟 %
  • 一个空格或单词“for”至少一次
  • (开始捕获)首字母大写
  • (结束捕获组)中的至少一项:
    • 单词字符、句号、男性/女性符号或撇号
      • 注意:如果您想捕捉其他“奇怪”的口袋妖怪字符,例如数字、冒号等,请将它们添加到此部分([\w\.♀♂'] 位)。
    • 或空格,但如果后跟大写字母
  • 逗号、空格或 & 符号,任意次数

除非更改,否则 Python 的内置 re 模块不支持重复的捕获组(我相信我做对了),所以我只是使用了 re.findall 并将它们组织成对(我从您的输入中替换了几个名称复杂的):

import re

str1 = '95% for Pikachu, 92% for Mr. Mime'
str2 = '70% for Paras & 100% Arcanine'
str3 = '99% Diglett, 40% Dugtrio'
str4 = "10% Squirtle, 100% for Farfetch'd"
str5 = '30% Metopod & 99% Nidoran♂'

pattern = r"(?:(?:\d+%(?: |for)+([A-Z](?:[\w\.♀♂']|(?: (?=[A-Z])))+))+)[, &]*"

# Find matches in each string, then unpack each list of
# matches into a flat list
all_matches = [match
               for s in [str1, str2, str3, str4, str5]
               for match in re.findall(pattern, s)]

# Pair up the matches
pairs = zip(all_matches[::2], all_matches[1::2])

for pair in pairs:
    print(pair)

然后打印出来:

('Pikachu', 'Mr. Mime')
('Paras', 'Arcanine')
('Diglett', 'Dugtrio')
('Squirtle', "Farfetch'd")
('Metopod', 'Nidoran♂')

另外,正如已经提到的,你确实在口袋妖怪名称中有一些拼写错误,但不幸的是,正则表达式不是正确的解决方法:)

【讨论】:

  • 如果我不想过滤掉♀♂ 怎么办?我可以使用r"(?:(?:\d+%(?: |for)+([A-Z](?:[\w\.']|(?: (?=[A-Z])))+))+)[, &]*" 吗??
  • 如果您对它们不感兴趣,可以忽略它们。看起来该模式仍会获取名称(无论它是在您的字符串中列出的第 1 位还是第 2 位),但不要使用性别标记。这样做的一个重要副作用是它会在遇到这些字符时停止匹配,因此如果它们后面还有其他内容,它们也会被忽略。
【解决方案2】:

由于您的字符串中似乎没有其他大写字母,您可以简单地使用[A-Z]\w+ 作为正则表达式。 见regex101

代码:

import re

str1 = '95% for Pikachu, 92% for Sandsherew'
str2 = '70% for Paras & 100% Arcanine'
str3 = '99% Diglett, 40% Dugtrio'
str4 = '10% Squirtle, 100% for Alakazam'
str5 = '30% Metopod & 99% Dewgong'

str_list = [str1, str2, str3, str4, str5]
regex = re.compile('[A-Z]\w+')
pokemon_list = []
for x in str_list:
    pokemon_list.append(re.findall(regex, x))
print(pokemon_list)

输出:

[['Pikachu', 'Sandsherew'], ['Paras', 'Arcanine'], ['Diglett', 'Dugtrio'], ['Squirtle', 'Alakazam'], ['Metopod', 'Dewgong']]

【讨论】:

  • 有些口袋妖怪的名字不仅仅包含字母,不确定操作是否也希望匹配这些名字(尽管我想不出任何不以大写字母开头的名字)
  • 我对口袋妖怪不太擅长,所以我不知道,但如果是这种情况,那么来自 OP 的示例选择得相当糟糕......
  • 使用 ([A-Z])[^,&\n]+ 可能会更好,因为 OP 明确提到它们以逗号或 & 结尾(然后 rstrip 任何尾随空格)
【解决方案3】:

如果您不想使用正则表达式并且不想依赖大写,另一种方法

def pokeFinder(strng):
    wordList = strng.split()
    pokeList = []
    for word in wordList:
        if not set('[~!@#$%^&*()_+{}":;\']+$').intersection(word) and 'for' not in word:
            pokeList.append(word.replace(',', ''))
    return pokeList

这不会添加带有特殊字符的单词。它也不会添加for 的单词。然后它会从找到的单词中删除逗号。

str2 的打印返回 ['Diglett', 'Dugtrio']


编辑 鉴于显然有两个单词和特殊字符的口袋妖怪,我对上述代码做了这个稍微复杂的版本

def pokeFinder(strng):
    wordList = strng.split()
    pokeList = []
    prevWasWord = False
    for word in wordList:
        if not set('%&').intersection(word) and 'for' not in word:
            clnWord = word.replace(',', '')
            if prevWasWord is True: # 2 poke in a row means same poke
                pokeList[-1] = pokeList[-1] + ' ' + clnWord
            else:
                pokeList.append(clnWord)
                prevWasWord = True
        else:
            prevWasWord = False
    return pokeList

如果没有“三字”口袋妖怪,并且 OP 设置的规则保持不变,这应该总是有效的。连续 2 次 poke 匹配添加到前一个 pokemon。

所以打印一串'30% for Mr. Mime & 20% for Type: Null' 得到 ['Mr. Mime', 'Type: Null']

【讨论】:

    【解决方案4】:

    使用积极的向后看,无论大小写如何,这都会起作用。

    (?<=\d\d% for )[A-Za-z]+|(?<=\d% for )[A-Za-z]+

    编辑:将其更改为在 Python 中工作。

    【讨论】:

    • 不起作用“+ 后视内的量词使其宽度不固定” python 需要固定宽度的后视。请参阅regex101 并且正如 OP 所说“全部以大写字母开头”
    • 你能澄清一下你的意思吗?这对我有用@LeoE
    • 你能显示代码吗?如果我尝试在我在答案中发布的代码中运行您的正则表达式 raise error("look-behind requires fixed-width pattern") sre_constants.error: look-behind requires fixed-width pattern,我会收到错误
    • @LeoE 你说得对,我是用 Regxr 测试的,试试那个。
    猜你喜欢
    • 1970-01-01
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 2019-03-14
    • 2023-01-07
    • 1970-01-01
    相关资源
    最近更新 更多