2013-04-13 3 views
2

У меня есть файл, который выглядит следующим образом:Multiline регулярное выражение соответствие

useless stuff 

fruit: apple 
fruit: banana 

useless stuff 

fruit: kiwi 
fruit: orange 
fruit: pear 

useless stuff 

Идея заключается в том, чтобы поймать все имена фруктов, в том порядке, в котором они появляются, так и группами. В примере выше, выход должен быть что-то вроде:

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

Я преуспеваем делает это путем перебора всех матчей за многострочного регулярное выражение '^fruit: (.+)$', и путем добавления названия фруктов одному и тому же данный список, если оказывается, что линии, где они были найдены, следуют друг за другом.

Однако это нецелесообразно для замещений на именах фруктов (отслеживание начального и конечного индексов совпадений становится обязательным), поэтому я бы предпочел сделать это в одном регулярном выражении.

Я попытался это:

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

Но это только возвращает одну строку.

Где я ошибаюсь?

+3

вам абсолютно необходимо сделать это с помощью регулярных выражений делать? – jamylak

+0

@jamylak: Я думаю, что это станет очень болезненным без регулярного выражения, а совпадающие шаблоны в реальном случае довольно сложны. – michaelmeyer

+0

Что это за линия, которую она находит? – allyourcode

ответ

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']] 
+0

Спасибо, что упомянул модуль 'regex', я обязательно его исповедую. Хотя в этом случае матч с двумя проходами кажется неизбежным. – michaelmeyer

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']] 
0

Я не большой поклонник использования регулярных выражений если вам не обязательно. Сделав шаг назад и посмотрев на ваш случай, мое первое наклонение состоит в том, чтобы подумать, не следует ли на самом деле массировать входные файлы на что-то вроде csv с помощью специализированного инструмента, такого как awk, перед подачей на 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 
1

Я думаю, вы будете видеть проблему, если вы сделаете внутреннюю группу, не захватывая так:

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

проблема в том, что каждое совпадение соответствует целым пучкам fruit: строк, но группа захвата (в вашем исходном soln) захватывает несколько раз. Так как группа захвата может иметь только одно значение, связанное с ней, она заканчивается последней захваченной подстрокой (я думаю, что выбор последней произволен, я не буду рассчитывать на это поведение).

+0

Спасибо, например, это облегчает понимание проблемы. – michaelmeyer

0

как насчет:

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

результат:

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

это может быть легко преобразован в [[ 'яблоко', 'банановой'], [ 'Киви', 'оранжевый', 'груша']]

example in ideone

+0

Это не работает, если в конце файла есть фрукты, за которыми не следует новая строка. http://ideone.com/DTpEAx – jamylak

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']]