Wyrażenie regularne pasujące do wielowierszowego bloku tekstu

105

Mam trochę problemów z uruchomieniem wyrażenia regularnego Pythona podczas dopasowywania do tekstu obejmującego wiele wierszy. Przykładowy tekst to ('\ n' to nowa linia)

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).

Chciałbym uchwycić dwie rzeczy: część „some_Varying_TEXT” i wszystkie wiersze tekstu pisanego wielkimi literami, które pojawiają się dwa wiersze poniżej w jednym ujęciu (mogę później usunąć znaki nowej linii). Próbowałem z kilkoma podejściami:

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

i wiele jego odmian bez powodzenia. Ta ostatnia wydaje się dopasowywać wiersze tekstu jeden po drugim, co nie jest tym, czego naprawdę chcę. Mogę złapać pierwszą część, nie ma problemu, ale nie mogę wyłapać 4-5 wierszy tekstu pisanego wielkimi literami. Chciałbym, aby match.group (1) był jakimś_Zmienny_Tekst, a grupa (2) była linią1 + linią2 + linią3 + itd. Aż do napotkania pustej linii.

Jeśli ktoś jest ciekawy, przypuszczalnie jest to sekwencja aminokwasów tworzących białko.

Jan
źródło
Czy w pliku jest coś jeszcze poza pierwszą linią i tekstem wielkimi literami? Nie jestem pewien, dlaczego miałbyś użyć wyrażenia regularnego zamiast dzielić cały tekst na znaki nowego wiersza i przyjmować pierwszy element jako „some_Varying_TEXT”.
UncleZeiv
2
tak, regex to niewłaściwe narzędzie do tego.
Twój przykładowy tekst nie ma >znaku wiodącego . Czy powinno?
MiniQuark,

Odpowiedzi:

114

Spróbuj tego:

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

Myślę, że Twoim największym problemem jest to, że oczekujesz, że kotwice ^i $będą pasować do wysuwów linii, ale tak się nie dzieje. W trybie multilinii ^dopasowuje pozycję bezpośrednio po nowej linii i $pozycję bezpośrednio poprzedzającą nową linię.

Należy również pamiętać, że znak nowej linii może składać się z wysuwu wiersza (\ n), powrotu karetki (\ r) lub powrotu karetki + wysuwu wiersza (\ r \ n). Jeśli nie masz pewności, że tekst docelowy używa tylko wysunięć wiersza, powinieneś użyć tej bardziej kompleksowej wersji wyrażenia regularnego:

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

BTW, nie chcesz tutaj używać modyfikatora DOTALL; polegasz na fakcie, że kropka pasuje do wszystkiego oprócz znaków nowej linii.

Alan Moore
źródło
Możesz zamienić drugą kropkę w wyrażeniu regularnym na [AZ], jeśli nie chcesz, aby to wyrażenie regularne pasowało do dowolnego pliku tekstowego z pustą drugą linią. ;-)
MiniQuark,
Mam wrażenie, że pliki docelowe będą zgodne z określonym (i powtarzającym się) wzorem pustych i niepustych wierszy, więc nie powinno być konieczne określanie [AZ], ale prawdopodobnie też nie zaszkodzi.
Alan Moore,
To rozwiązanie działało pięknie. Na marginesie przepraszam, ponieważ oczywiście nie wyjaśniłem wystarczająco sytuacji (a także za spóźnienie tej odpowiedzi). Dzięki za pomoc!
stycznia
21

To zadziała:

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

Przydatne może być wyjaśnienie tego wyrażenia regularnego: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Pierwszy znak ( ^) oznacza „zaczynając od początku wiersza”. Należy pamiętać, że nie pasuje do samego znaku nowej linii (tak samo dla $: oznacza to „tuż przed nową linią”, ale nie pasuje do samego znaku nowej linii).
  • Następnie (.+?)\n\noznacza „pasuje jak kilka znaków, jak to możliwe (wszystkie znaki są dozwolone) aż dwie nowe linie”. Wynik (bez nowych linii) jest umieszczany w pierwszej grupie.
  • [A-Z]+\noznacza „dopasuj jak najwięcej wielkich liter, aż dojdziesz do nowej linii. To definiuje, co nazywam linią tekstową .
  • ((?:textline)+) oznacza dopasowywanie jednej lub więcej linii tekstu, ale nie umieszczaj każdej linii w grupie. Zamiast umieścić wszystkie te TextLine w jednej grupie.
  • Możesz dodać \nkońcówkę w wyrażeniu regularnym, jeśli chcesz wymusić podwójny znak nowej linii na końcu.
  • Ponadto, jeśli nie jesteś pewien, jaki typ nowej linii otrzymasz ( \nlub \rlub \r\n), po prostu popraw wyrażenie regularne, zastępując każde wystąpienie \nprzez (?:\n|\r\n?).
MiniQuark
źródło
1
match () zwraca tylko jedno dopasowanie na samym początku tekstu docelowego, ale operator operacyjny powiedział, że w jednym pliku będą setki dopasowań. Myślę, że zamiast tego wolałbyś Finditer ().
Alan Moore,
6

Gdyby każdy plik miał tylko jedną sekwencję aminokwasów, w ogóle nie używałbym wyrażeń regularnych. Po prostu coś takiego:

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
MiniQuark
źródło
Zdecydowanie najłatwiejszy sposób, jeśli byłby tylko jeden, a także działa z większą liczbą, jeśli doda się więcej logiki. Jednak w tym konkretnym zbiorze danych jest około 885 białek i czułem, że wyrażenie regularne powinno sobie z tym poradzić.
stycznia
4

odnaleźć:

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

\ 1 = jakiś_mienny_tekst

\ 2 = wiersze wszystkich WIELKICH LITER

Edytuj (dowód, że to działa):

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])
Jason Coon
źródło
Niestety, to wyrażenie regularne będzie również dopasowywać grupy wielkich liter oddzielone pustymi wierszami. To może nie być jednak wielka sprawa.
MiniQuark,
Wygląda na to, że coonj lubi pliki FASTA. ;)
Andrew Dalke
4

Poniżej znajduje się wyrażenie regularne pasujące do wielowierszowego bloku tekstu:

import re
result = re.findall('(startText)(.+)((?:\n.+)+)(endText)',input)
Punnerud
źródło
1

Moja preferencja.

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 )

W tym momencie masz someVaryingText jako łańcuch, a kwasy jako listę łańcuchów. Możesz to zrobić"".join( acids ) pojedynczy ciąg.

Uważam to za mniej frustrujące (i bardziej elastyczne) niż wyrażenia regularne wielowierszowe.

S.Lott
źródło