【问题标题】:Regex to capture digits in a list enclosed in other characters正则表达式捕获包含在其他字符中的列表中的数字
【发布时间】:2023-03-15 06:42:01
【问题描述】:

嘿,

所以,我有以下字符串(我删除了很多数字,因为我们不需要它们):

编辑:见下文

text = """
<--
first+second=?

first:
146,399,163,403,170,379,147,394,146,399

second:
156,141,136,156,136

-->"""

我正在尝试找到一种方法来捕获列表中的第一个数字和另一个列表中的第二个数字(使用 python,re.findall())。 所以我的目标是让python返回一个这样的列表(对于“first:”和“second:”之间的“first”列表:

[146, 399, 163, 403,170,379,147,394,146,399]

不幸的是,我没有设法创建一个正则表达式来捕获用逗号/换行符括起来的一组数字,它们本身被包含在第一个和第二个(或其他)中。这样做时,我只能设法捕获我使用的边界内的第一组或最后一组数字。

这是我使用的错误表达之一。我已经尝试了很多其他方法,并在捕获组和其他东西上使用了量词,但我似乎无法做到正确。

first:.+(\d{2,3}).+second:

我知道首先捕获完整列表并将其拆分会更容易,但我想更好地理解正则表达式,因此我坚持使用它。


编辑:对不起...我应该添加更多的初始字符串,我缩短了太多。问题是,列表分为几行,就像这样(但仍然缩短了):

text = """
<!--
first:
146,399,163,403,170,393,169,391,166,386,170,381,170,371,170,355,169,346,167,335,170,329,170,320,170,
310,171,301,173,290,178,289,182,287,188,286,190,286,192,291,194,296,195,305,194,307,191,312,190,316,
332,155,348,156,353,153,366,149,379,147,394,146,399

second:
156,141,165,135,169,131,176,130,187,134,191,140,191,146,186,150,179,155,175,157,168,157,163,157,159,
77,155,81,148,87,140,96,138,105,141,110,136,111,126,113,129,118,117,128,114,137,115,146,114,155,115,
158,121,157,128,156,134,157,136,156,136
-->
""""

【问题讨论】:

  • 你的编程语言?
  • 我使用的是 python 3.5
  • 在选择代码后使用按钮 {} 来格式化代码。不要为此使用引用环境。我在您的问题中没有看到 ANY stringg。 python 字符串看起来像 myNumbers = '1,2,3,4'myOtherNumbers = "11,12,13,14"
  • 对于正则表达式,请给我们您的确切(可能缩短)数据 - 否则我们无法适应正则表达式。
  • 对不起,字符串确实不正确。我指的是包含我希望在列表中捕获的内容,即“第一:”和“第二:”。整个代码块确实是我正在操作的字符串。数字列表要长得多,并且两个列表之前和之后都有更多的文本。如果您认为它相关,我会添加它。

标签: regex python-3.x


【解决方案1】:

注意 - 在本次编辑中,有一些知识渊博的用户给出了一些答案,但到目前为止,这似乎是唯一一个用一行代码来回答您的问题的答案。

你可以试试这个说法:

x = re.findall(r'(\w+?):\s+((?:\d+,?)+)', text)

在打印x 时返回以下数组:

[('first', '146,399,163,403,170,379,147,394,146,399'), ('second', '156,141,136,156,136')]

解释-

这个正则表达式不仅可以像您的问题状态那样找到第一个和第二个,而且如果您的代码在任何时间点包含,说:

third:
123, 124, 12, 1

或者类似的东西,正则表达式甚至可以匹配。

x 返回一个包含两个元组的数组 - 每个元组都有第一个元素作为名称,即firstsecond 等等,而每个第二个元素是实际的数字字符串。

正则表达式字符串开头的r 表示它是一个原始字符串,基本上放弃了那些讨厌的双反斜杠。
(\w+?) 是与@987654330 等数字之前的文本匹配的组1 @、second 甚至其他文本(如果您的示例输入包含它)。
: 表示第一个或第二个等文本后跟一个冒号,
\s+ 匹配一个或多个空格(在在这种情况下,甚至换行符)
((?:\d+,?)+) 是包含实际数字字符串的第二组
(?:\d+,?) 是一个非捕获组,它告诉正则表达式匹配一个或多个数字(可能后跟一个逗号),非捕获组后面的+ 表示这个序列将重复一次或多次。


哦,还有一件事 -

要访问所需的匹配项 - 无论是标签 firstsecond 等还是它们各自的数字,您都可以使用:

for match in x:
    print(match[0])
    print(match[1])
    print()

本质上,match 是包含[0] 的标签和[1] 的数字的元组


编辑 1 -

要捕获您最近指定的多行数字,您只需将\n? 添加到您的正则表达式中,如下所示:

x = re.findall(r'(\w+?):\s+((?:\d+,?\n?)+)', text)

与旁边的可选空格匹配。但是,有一个问题:打印时,match[1] 看起来像:

146,399,163,403,170,393,169,391,166,386,170,381,170,371,170,355,169,346,167,335,170,329,170,320,170,
310,171,301,173,290,178,289,182,287,188,286,190,286,192,291,194,296,195,305,194,307,191,312,190,316,
332,155,348,156,353,153,366,149,379,147,394,146,399,1111

match 本身看起来像:

('first', '146,399,163,403,170,393,169,391,166,386,170,381,170,371,170,355,169,346,167,335,170,329,170,320,170,\n310,171,301,173,290,178,289,182,287,188,286,190,286,192,291,194,296,195,305,194,307,191,312,190,316,\n332,155,348,156,353,153,366,149,379,147,394,146,399,1111\n')

对于x的第一个元素。

请注意匹配中物理\ns 的存在,这会干扰您想要进行的任何算术计算。

【讨论】:

  • 我喜欢它——对整数的后处理可以在以后进行。再接近我的尝试;)+1
  • 谢谢!。所以,我在缩短我正在使用的文本时犯了一个错误:列表有好几行。为了解决这个问题,我在(?:,?|,\n) 中更改了您最后一个非捕获组的,?,但它仍然只捕获第一行,我看不到我错过了什么。我不需要重复表达式,\n,因为,? 在列表末尾。另外,\s也不起作用。
  • @Naboochodonosor 不用担心,我已经相应地更新了我的答案,但是新的正则表达式 还将捕获回车键的 \n,因此执行任何算术运算都将是棘手。
  • @Naboochodonosor 希望你不需要:)
【解决方案2】:

你可以作弊:

text = """
<--
first+second=?

first:
146,399,163,403,170,379,147,394,146,399

second:
156,141,136,156,136

-->"""

import re

first = list(map(int,re.findall(r"\d+",text[text.find("first:"):text.find("second:")])))
second = list(map(int,re.findall(r"\d+",text[text.find("second:"):])))

print(first)
print(second)
print(first+second)

输出:

[146, 399, 163, 403, 170, 379, 147, 394, 146, 399]
[156, 141, 136, 156, 136]
[146, 399, 163, 403, 170, 379, 147, 394, 146, 399, 156, 141, 136, 156, 136]

作弊是:

  • 您只需将 text 的部分内容提供给您想要的正则表达式
  • 您对text 进行切片,使其仅使用'first:''second:' 之间的文本
  • 然后在'second:' 之后将其切片以供全部使用

给你单独的列表。然后你把它们加在一起。

list(map(int, &lt;regex_cmd&gt; )) 只是它们以整数而不是字符串的形式出现。


如果您只需要文本中的所有整数,这也可以:

def isInt(t):
    try:
        return int(t)
    except:
        pass


splitted = [int(l) for k in text.split("\n") 
            for p in k.split(" ") 
            for l in p.split(",") if isInt(l) is not None]

print(splitted)

输出:

[146, 399, 163, 403, 170, 379, 147, 394, 146, 399, 156, 141, 136, 156, 136]

【讨论】:

  • 您可能会考虑使用ast.literal_eval() 而不是编写自己的函数:)
  • 谢谢。在使用 map 和像这样的列表完成之间:[int(x) for x in re.findall(r"\d+", text[text.find("first:"):text.find("second:")])],最有效和 Pythonic 的方式是什么?
  • @Naboochodonosor list comp 看起来更容易,地图更特别 ;) 选择你更舒服的那个。 map 通常具有提供生成器的优势,但在您的情况下,您会立即将地图结果消耗到列表中,所以我认为这无关紧要
【解决方案3】:

使用两步法:

  1. 使用 dict 理解通过正则表达式获取项目
  2. 将通过ast.literal_eval()获取的字符串转换为列表


正则表达式可以是:
^                         # start of the line
(?P<group>[a-z]+):[\n\r]  # a group named "group" with lowercase letters, 
                          # followed by a colon
(?P<lst>(?:\d+,?\s*)+)    # digits, commas and spaces

a demo on regex101.com


Python:
import re
from ast import literal_eval

rx = re.compile(r'''
    ^
    (?P<group>[a-z]+):[\n\r]
    (?P<lst>(?:\d+,?\s*)+)
    ''', re.M | re.X)

result = {m.group('group'): literal_eval('[{}]'.format(m.group('lst'))) 
          for m in rx.finditer(text)}
print(result)

产生一个字典:

{'first': [146, 399, 163, 403, 170, 393, 169, 391, 166, 386, 170, 381, 170, 371, 170, 355, 169, 346, 167, 335, 170, 329, 170, 320, 170, 310, 171, 301, 173, 290, 178, 289, 182, 287, 188, 286, 190, 286, 192, 291, 194, 296, 195, 305, 194, 307, 191, 312, 190, 316, 332, 155, 348, 156, 353, 153, 366, 149, 379, 147, 394, 146, 399], 
 'second': [156, 141, 165, 135, 169, 131, 176, 130, 187, 134, 191, 140, 191, 146, 186, 150, 179, 155, 175, 157, 168, 157, 163, 157, 159, 77, 155, 81, 148, 87, 140, 96, 138, 105, 141, 110, 136, 111, 126, 113, 129, 118, 117, 128, 114, 137, 115, 146, 114, 155, 115, 158, 121, 157, 128, 156, 134, 157, 136, 156, 136]}

之后,您可以通过 result['first'] 访问您的项目。

【讨论】:

    【解决方案4】:

    如果您对第一组和第二组的单独通行证没问题,那么就可以了:

    第一组:(?&lt;=first:.+)(\d{2,3})(?=.+second:)

    第二组:(?&lt;=second:.+)(\d{2,3})

    这些使用lookahead 和lookbehind 模式来检查“first:”和“second:”标签相对于被匹配数字的位置。

    【讨论】:

    • 谢谢 :) 。但是,它返回一个错误,因为后视似乎需要一个固定的长度,这将禁止使用非精确的量词。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多