繁体   English   中英

多行正则表达式匹配

[英]Multiline regex matching

我有一个看起来像这样的文件:

useless stuff

fruit: apple
fruit: banana

useless stuff

fruit: kiwi
fruit: orange
fruit: pear

useless stuff

想法是按水果的出现顺序和组来捕获所有水果的名称。 在上面的示例中,输出必须类似于:

[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

我通过遍历多行正则表达式'^fruit: (.+)$'所有匹配项,并通过将水果名称添加到相同的给定列表(如果发现它们所在的行似乎彼此跟随'^fruit: (.+)$'成功地做到这一点。

但是,这对于在水果名称上进行替换是不切实际的(保持对比赛开始和结束索引的跟踪成为必需的),所以我宁愿在单个正则表达式中进行此操作。

我已经试过了:

re.findall(r'(?:^fruit: (.+)$\n)+', thetext, re.M)

但是它只返回一行。

我哪里错了?

这样您就可以保留正则表达式,因为您稍后可能会需要更复杂的表达式:

>>> import re
>>> from itertools import groupby
>>> with open('test.txt') as fin:
        groups = groupby((re.match(r'(?:fruit: )(.+)', line) for line in fin),
                         key=bool) # groups based on whether each line matched
        print [[m.group(1) for m in g] for k, g in groups if k]
        # prints each matching group


[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

没有正则表达式:

>>> with open('test.txt') as f:
        print [[x.split()[1] for x in g]
               for k, g in groupby(f, key=lambda s: s.startswith('fruit'))
               if k]


[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

我认为,如果像这样使内部组不被捕获,您将看到问题:

re.findall(r'(?:^fruit: (?:.+)$\n)+', thetext, re.M)
# result:
['fruit: apple\nfruit: banana\n', 'fruit: kiwi\nfruit: orange\nfruit: pear\n']

问题在于每个匹配项都匹配一整串fruit:行,但是捕获组(在您的原始解决方案中)捕获了多次。 由于捕获组只能有一个与之关联的值,因此它以捕获的最后一个子字符串结尾(我认为对last的选择是任意的;我不会指望这种行为)。

其他方式:

import re
with open('input') as file:
    lines = "".join(file.readlines())
    fruits = [[]]
    for fruit in re.findall(r'(?:fruit: ([^\n]*))|(?:\n\n)', lines, re.S):
        if fruit == '': 
            if len(fruits[-1]) > 0:
                fruits.append([])
        else:
            fruits[-1].append(fruit)
    del fruits[-1]
    print fruits

产量

[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

您不能在正则表达式中以这种方式进行“分组”,因为通常组仅捕获其最新匹配项。 一种解决方法是从字面上重复一个组:

matches = re.findall(r'(?m)(?:^fruit: (.+)\n)(?:^fruit: (.+)\n)?(?:^fruit: (.+)\n)?', text)
# [('apple', 'banana', ''), ('kiwi', 'orange', 'pear')]

如果这适合您的任务(例如,不超过5-6个小组),则可以轻松地即时生成此类表达式。 如果没有,则唯一的选择是两遍匹配(我想这与您已经拥有的相似):

matches = [re.findall(': (.+)', x) 
    for x in re.findall(r'(?m)((?:^fruit: .+\n)+)', text)]
# [['apple', 'banana'], ['kiwi', 'orange', 'pear']]

非标准(尚) 正则表达式模块提供了一种有趣的方法,称为“捕获”。 m.captures(n)返回一个组的所有匹配项,不仅像m.group(n)一样,还返回最新的m.group(n)

import regex
matches = [x.captures(2) for x in regex.finditer(r'(?m)((?:^fruit: (.+)\n)+)', text)]
# [['apple', 'banana'], ['kiwi', 'orange', 'pear']]

除非绝对必要,否则我不是使用正则表达式的忠实拥护者。 向后退一步,看看您的情况,我的第一个倾向是考虑是否实际上不应该在将输入文件输入python之前使用awk之类的专用工具将输入文件输入csv之类的东西。

话虽如此,您仍然可以使用清晰的无正则表达式python来完成您想要的工作。 一个例子(我相信可以在不牺牲透明度的情况下减少它):

# newlst keeps track of whether you should start a new sublist
newlst=False
# result is the end result list of lists
result = []
# lst is the sublist which gets reset every time a grouping concludes
lst = []

with open('input.txt') as f:
    for line in f.readlines():
        # is the first token NOT a fruit?
        if line.split(':')[0] != 'fruit':
            # if so, start a new sublist
            newlst=True
            # just so we don't append needless empty sublists
            if len(lst) > 0: result.append(lst)
            # initialise a new sublist, since last line wasn't a fruit and
            # this implies a new group is starting
            lst = []
        else:
            # first token IS a fruit. So append it to the sublist
            lst.append(line.split()[1])

print result

怎么样:

re.findall(r'fruit: ([\w]+)\n|[^\n]*\n', str, re.M);

结果:

['', '', 'apple', 'banana', '', '', '', 'kiwi', 'orange', 'pear', '']

这可以很容易地转换为[['apple','banana'],['kiwi','orange','pear']]

ideone中的例子

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM