2

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

useless stuff

fruit: apple
fruit: banana

useless stuff

fruit: kiwi
fruit: orange
fruit: pear

useless stuff

这个想法是按照它们出现的顺序和分组来捕获所有水果名称。对于上面的示例,输出必须类似于:

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

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

但是,这对于对水果名称进行替换是不切实际的(跟踪匹配开始和结束索引成为强制性的),所以我更愿意在单个正则表达式中执行此操作。

我试过这个:

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

但它只返回一行。

我哪里错了?

4

6 回答 6

1

这允许您保留您的正则表达式,正如您所说,您以后可能需要更复杂的表达式:

>>> 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']]
于 2013-04-13T10:26:09.333 回答
1

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

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

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

于 2013-04-13T10:38:34.173 回答
1

另一种方式:

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']]
于 2013-04-13T10:55:35.520 回答
1

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

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)

import regex
matches = [x.captures(2) for x in regex.finditer(r'(?m)((?:^fruit: (.+)\n)+)', text)]
# [['apple', 'banana'], ['kiwi', 'orange', 'pear']]
于 2013-04-13T11:01:51.180 回答
0

除非您绝对必须这样做,否则我不是使用正则表达式的忠实粉丝。退后一步,看看你的情况,我的第一个倾向是考虑你是否实际上不应该在将输入文件输入 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
于 2013-04-13T10:33:36.050 回答
0

怎么样:

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

结果:

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

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

ideone 中的示例

于 2013-04-13T10:49:33.057 回答