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