【问题标题】:How can I get the number of groups to vary depending on the number of lines?如何使组数根据行数而变化?
【发布时间】:2016-08-19 09:11:06
【问题描述】:

我有这个正则表达式:^:([^:]+):([^:]*),它在 this regex101 link 中工作。

现在,在 Python 中,我有这个:

def get_data():
    data = read_mt_file()
    match_fields = re.compile('^:([^:]+):([^:]*)', re.MULTILINE)

    fields = re.findall(match_fields, data)

    return fields

对于包含来自 regex101 的数据的文件,返回:

[('1', 'text\ntext\n\n'), ('20', 'text\n\n'), ('21', 'text\ntext\ntext\n\n'), ('22', ' \n\n'), ('25', 'aa\naa\naaaaa')]

现在,这没关系,但我想更改正则表达式,以便我可以让组数根据行数而变化。含义:

  • 对于第一行,现在我得到了两组:
    1. 1
    2. text\ntext\n\n

我想改为:

  1. 1
  2. ((text\n), (text\n\n)) 子组中。不知何故,我需要知道它们都属于 1 字段,但是是单独的行。

因此,在 python 中,该文件的期望结果是:

[('1', '(text\n), (text\n\n)'), ('20', 'text\n\n'), ('21', '(text\n), (text\n), (text\n\n)'), ('22', ' \n\n'), ('25', '(aa\n), (aa\n), (aaaaa)')]

这可以用正则表达式吗?这可以通过一些不错的字符串操作来实现吗?

【问题讨论】:

  • 您不能让正则表达式返回可变数量的组。您可以执行((\w*)(?:\s(\w+))?) 之类的操作将文本拆分为第 3 组和第 4 组,但更好的解决方案是使用您拥有的正则表达式,然后使用 (?<=\n)(?=\w) 之类的内容拆分第 2 组的内容。
  • 使用re 正则表达式,您可能会得到块,但是您需要对结果执行额外的操作。使用 PyPi 正则表达式模块,您可以通过访问重复捕获通过 1 个正则表达式操作来获得它。
  • @WiktorStribiżew 我是否必须对结果执行其他额外操作并不重要,只要它们更接近我需要的结果:P
  • 检查ideone.com/hO7SyM - 也许这就是你需要的?结果是一个列表列表 - [['1', 'text\n', 'text\n\n'], ['20', 'text\n\n'], ['21', 'text\n', 'text\n', 'text\n\n'], ['22', ' \n\n'], ['25', 'aa\n', 'aa\n', 'aaaaa']]
  • print([(x, "({})".format(re.sub(r".+(?!\n*$)\n+", r"\g<0>), (", y))) for x, y in p.findall(s)]) - ideone.com/DPP9mq

标签: python regex python-3.x split


【解决方案1】:

要做你想做的事,你需要另一个正则表达式。 这是因为re.match 只匹配它匹配的最后一项:

>>> re.match(r'(\d)+', '12345').groups()
('5',)

您需要使用两个,而不是使用一个正则表达式。 您目前正在使用的那个,然后是一个匹配所有“子组”的那个,例如re.findall。 您可以通过简单地匹配不是\n 的任何内容然后匹配任意数量的\n 来获得这些子组。

所以你可以使用诸如[^\n]+\n*这样的正则表达式:

>>> re.findall(r'[^\n]+\n*', 'text\ntext')
['text\n', 'text']
>>> re.findall(r'[^\n]+\n*', 'text\ntext\n\n')
['text\n', 'text\n\n']
>>> re.findall(r'[^\n]+\n*', '')
[]

【讨论】:

    【解决方案2】:

    您可以使用一个简单的技巧:在与您的正则表达式匹配后,对第 2 组值运行 .+\n* 正则表达式:

    import re
    p = re.compile(r'^:([^:]+):([^:]+)', re.MULTILINE)
    s = ":1:text\ntext\n\n:20:text\n\n:21:text\ntext\ntext\n\n:22: \n\n:25:aa\naa\naaaaa"
    print([[x.group(1)] + re.findall(r".+\n*", x.group(2)) for x in p.finditer(s)])
    

    这里,

    • p.finditer(s) 使用您的正则表达式查找字符串中的所有匹配项
    • [x.group(1)] - 从第一组内容创建的列表
    • re.findall(r".+\n*", x.group(2)) - 从第 2 组内容中获取单独的行(带有尾随换行符,0 个或更多)
    • [] + re.findall - 将列表合并为 1。

    结果是

    [['1', 'text\n', 'text\n\n'], ['20', 'text\n\n'], ['21', 'text\n', 'text\n', 'text\n\n'], ['22', ' \n\n'], ['25', 'aa\n', 'aa\n', 'aaaaa']]

    另一种方法:将所有子字符串与您的模式匹配,然后使用re.sub 在以可选换行符结尾的行之间添加), (

    [(x, "({})".format(re.sub(r".+(?!\n*$)\n+", r"\g<0>), (", y))) for x, y in p.findall(s)]
    

    结果:

    [('1', '(text\n), (text\n\n)'), ('20', '(text\n\n)'), ('21', '(text\n), (text\n), (text\n\n)'), ('22', '( \n\n)'), ('25', '(aa\n), (aa\n), (aaaaa)')]

    Python 3 demo

    这里:

    • p.findall(s) - 使用正则表达式以包含捕获组内容的元组列表的形式获取所有匹配项
    • (x, "({})".format(re.sub(r".+(?!\n*$)\n+", r"\g&lt;0&gt;), (", y))) - 从第 1 组内容和第 2 组内容创建一个元组,这些内容使用re.sub 进行了一些修改,如下所述
    • .+(?!\n*$)\n+ - 匹配 1+ 个字符而不是换行符的模式,如果它们不在字符串的末尾,则匹配 1+ 个换行符。如果它们位于字符串的末尾,则不会进行替换(以避免末尾出现, ())。替换字符串中的\g&lt;0&gt; 将整个匹配重新插入到结果字符串中,并将), ( 附加到它。

    【讨论】:

    • 如果我遗漏了什么,或者有什么不清楚的地方,请告诉我。
    猜你喜欢
    • 2023-03-05
    • 2019-11-19
    • 2015-02-22
    • 1970-01-01
    • 2019-08-16
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多