Регулярний вираз, що відповідає багаторядковому блоку тексту


105

У мене виникають труднощі з тим, щоб згенерувати Python для роботи, коли він відповідає тексту, що охоплює кілька рядків. Приклад тексту: (\ \ n - це новий рядок)

some Varying TEXT\n
\n
DSJFKDAFJKDAFJDSAKFJADSFLKDLAFKDSAF\n
[more of the above, ending with a newline]\n
[yep, there is a variable number of lines here]\n
\n
(repeat the above a few hundred times).

Я хотів би зафіксувати дві речі: частину 'some_Varying_TEXT' і всі рядки великого тексту, що надходить на два рядки під одним захопленням (я можу викреслити символи нового рядка пізніше). Я спробував декілька підходів:

re.compile(r"^>(\w+)$$([.$]+)^$", re.MULTILINE) # try to capture both parts
re.compile(r"(^[^>][\w\s]+)$", re.MULTILINE|re.DOTALL) # just textlines

і безліч варіацій цього документа без удачі. Остання, здається, відповідає рядкам тексту один за одним, а це не те, що я дійсно хочу. Я можу спіймати першу частину, без проблем, але я не можу наздогнати 4-5 рядків великого тексту. Я хотів би, щоб match.group (1) був some_Varying_Text, а група (2) була line1 + line2 + line3 + тощо, поки не з’явиться порожній рядок.

Якщо хтось цікавий, його повинна бути послідовність амінокислот, що складають білок.


Чи є ще щось у файлі, окрім першого рядка та великого тексту? Я не впевнений, чому б ви використовували регулярний вираз, а не розділяти весь текст на символи нового рядка і вважати перший елемент "some_Varying_TEXT".
UncleZeiv

2
так, регулярний вираз - це неправильний інструмент для цього.

Ваш зразок тексту не має провідного >символу. Чи слід?
MiniQuark

Відповіді:


114

Спробуйте це:

re.compile(r"^(.+)\n((?:\n.+)+)", re.MULTILINE)

Я думаю, що ваша найбільша проблема полягає в тому, що ви очікуєте, що ^і $якіри будуть відповідати лінії харчування, але вони цього не роблять. У багаторядковому режимі, ^відповідає положенню безпосередньо наступного символ нового рядка і $відповідає положенню безпосередньо передує символ нового рядка.

Також пам’ятайте, що новий рядок може складатися з передачі рядка (\ n), повернення каретки (\ r) або повернення каретки + linefeed (\ r \ n). Якщо ви не впевнені, що цільовий текст використовує лише канали рядків, вам слід скористатись цією інклюзивнішою версією регулярного виразу:

re.compile(r"^(.+)(?:\n|\r\n?)((?:(?:\n|\r\n?).+)+)", re.MULTILINE)

До речі, ви не хочете використовувати тут модифікатор DOTALL; ви покладаєтесь на те, що крапка відповідає всім, крім нових рядків.


Ви можете замінити другу крапку в регулярному вираженні на [AZ], якщо ви не хочете, щоб цей регулярний вираз збігався майже з будь-яким текстовим файлом із порожнім другим рядком. ;-)
MiniQuark

Моє враження, що цільові файли відповідатимуть певній (і повторюваній) схемі порожніх та непорожніх рядків, тому вказувати [AZ] не слід, але ймовірно, це також не зашкодить.
Алан Мур

Це рішення спрацювало прекрасно. Я відкидаю вибачення, оскільки я, очевидно, недостатньо прояснив ситуацію (а також запізніла у цій відповіді). Спасибі за вашу допомогу!
Jan

21

Це спрацює:

>>> import re
>>> rx_sequence=re.compile(r"^(.+?)\n\n((?:[A-Z]+\n)+)",re.MULTILINE)
>>> rx_blanks=re.compile(r"\W+") # to remove blanks and newlines
>>> text="""Some varying text1
...
... AAABBBBBBCCCCCCDDDDDDD
... EEEEEEEFFFFFFFFGGGGGGG
... HHHHHHIIIIIJJJJJJJKKKK
...
... Some varying text 2
...
... LLLLLMMMMMMNNNNNNNOOOO
... PPPPPPPQQQQQQRRRRRRSSS
... TTTTTUUUUUVVVVVVWWWWWW
... """
>>> for match in rx_sequence.finditer(text):
...   title, sequence = match.groups()
...   title = title.strip()
...   sequence = rx_blanks.sub("",sequence)
...   print "Title:",title
...   print "Sequence:",sequence
...   print
...
Title: Some varying text1
Sequence: AAABBBBBBCCCCCCDDDDDDDEEEEEEEFFFFFFFFGGGGGGGHHHHHHIIIIIJJJJJJJKKKK

Title: Some varying text 2
Sequence: LLLLLMMMMMMNNNNNNNOOOOPPPPPPPQQQQQQRRRRRRSSSTTTTTUUUUUVVVVVVWWWWWW

Деякі пояснення щодо цього регулярного виразу можуть бути корисними: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Перший персонаж (^ ) означає "починати на початку рядка". Будьте в курсі, що він не відповідає самому новому рядку (те саме для $: це означає "безпосередньо перед новим рядком", але він не відповідає самому новому рядку).
  • Тоді (.+?)\n\nозначає "зіставити якомога менше символів (усі символи дозволені), поки ви не досягнете двох нових рядків". Результат (без нових рядків) ставиться в першу групу.
  • [A-Z]+\nозначає "відповідати якомога більше великих літер, поки ви не досягнете нового рядка. Це визначає те, що я буду називати текстовою лінією .
  • ((?:текстова лінія)+) означає, що відповідає одній або більше текстових ліній, але не слід ставити кожен рядок у групу. Замість цього помістіть все в об'єкти TextLine в однієї групи.
  • Ви можете додати фінал \n у звичайному виразі, якщо хочете виконати подвійний новий рядок в кінці.
  • Крім того, якщо ви не впевнені в тому, який тип нового рядка ви отримаєте ( \nабо \rабо \r\n), просто зафіксуйте регулярний вираз, замінивши кожен вигляд \nна (?:\n|\r\n?).

1
match () повертає лише одну відповідність на самому початку цільового тексту, але ОП заявила, що буде відповідати сотні матчів на файл. Я думаю, ви б хотіли знайти Finditer () замість цього.
Алан Мур,

6

Якщо кожен файл містить лише одну послідовність амінокислот, я б взагалі не використовував регулярні вирази. Просто щось подібне:

def read_amino_acid_sequence(path):
    with open(path) as sequence_file:
        title = sequence_file.readline() # read 1st line
        aminoacid_sequence = sequence_file.read() # read the rest

    # some cleanup, if necessary
    title = title.strip() # remove trailing white spaces and newline
    aminoacid_sequence = aminoacid_sequence.replace(" ","").replace("\n","")
    return title, aminoacid_sequence

Однозначно найпростіший спосіб, якби був лише один, і його також можна працювати з більшою кількістю, якщо додано ще трохи логіки. У цьому конкретному наборі даних є приблизно 885 білків, і я вважав, що регулярний вираз повинен бути в змозі впоратися з цим.
Jan

4

знайти:

^>([^\n\r]+)[\n\r]([A-Z\n\r]+)

\ 1 = деякий_ варінг_текст

\ 2 = рядки всіх CAPS

Редагувати (доказ того, що це працює):

text = """> some_Varying_TEXT

DSJFKDAFJKDAFJDSAKFJADSFLKDLAFKDSAF
GATACAACATAGGATACA
GGGGGAAAAAAAATTTTTTTTT
CCCCAAAA

> some_Varying_TEXT2

DJASDFHKJFHKSDHF
HHASGDFTERYTERE
GAGAGAGAGAG
PPPPPAAAAAAAAAAAAAAAP
"""

import re

regex = re.compile(r'^>([^\n\r]+)[\n\r]([A-Z\n\r]+)', re.MULTILINE)
matches = [m.groups() for m in regex.finditer(text)]

for m in matches:
    print 'Name: %s\nSequence:%s' % (m[0], m[1])

На жаль, цей регулярний вираз також буде відповідати групам великих літер, розділених порожніми рядками. Це, мабуть, не буде великою справою.
MiniQuark

Схоже, що coonj любить файли FASTA. ;)
Ендрю Далке

4

Далі подано регулярний вираз, що відповідає багаторядковому блоку тексту:

import re
result = re.findall('(startText)(.+)((?:\n.+)+)(endText)',input)

1

Мої переваги.

lineIter= iter(aFile)
for line in lineIter:
    if line.startswith( ">" ):
         someVaryingText= line
         break
assert len( lineIter.next().strip() ) == 0
acids= []
for line in lineIter:
    if len(line.strip()) == 0:
        break
    acids.append( line )

На даний момент у вас є рядVaryingText як рядок, а кислоти - як список струн. Можна зробити "".join( acids )для створення однієї струни.

Я вважаю це менш неприємним (і гнучкішим), ніж багаторядкові регекси.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.