我有一个看起来像这样的文件:
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)
但它只返回一行。
我哪里错了?
答案 0 :(得分: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']]
答案 1 :(得分:1)
我认为你会看到问题,如果你让内部组不像这样:
re.findall(r'(?:^fruit: (?:.+)$\n)+', thetext, re.M)
# result:
['fruit: apple\nfruit: banana\n', 'fruit: kiwi\nfruit: orange\nfruit: pear\n']
问题是每个匹配都匹配了一大堆fruit:
行,但捕获组(在原始soln中)会多次捕获。由于捕获组只能有一个与之关联的值,因此最终会捕获最后一个捕获的子字符串(我认为最后一个选择是任意的;我不会指望这种行为)。
答案 2 :(得分: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']]
答案 3 :(得分: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']]
非标准(尚)regex模块提供了一种名为“捕获”的有趣方法。 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']]
答案 4 :(得分:0)
除非你绝对必须,否则我不喜欢使用正则表达式。向后退一步,看看你的情况,我的第一个倾向是想一想你是否应该在输入文件之前使用像awk这样的专用工具将输入文件按照csv进行按摩,然后再将其输入到python中。
话虽如此,你当然可以使用清晰的无正则表达式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
答案 5 :(得分:0)
怎么样:
re.findall(r'fruit: ([\w]+)\n|[^\n]*\n', str, re.M);
结果:
['', '', 'apple', 'banana', '', '', '', 'kiwi', 'orange', 'pear', '']
这可以很容易地转换为[['apple','banana'],['kiwi','orange','pear']]