2009-02-25 22 views
60

Sto riscontrando un po 'di problemi nel far funzionare una regex di Python durante la corrispondenza con il testo che si estende su più righe. Il testo esempio è ('\ n' è un ritorno a capo)Espressione regolare che corrisponde a un blocco di testo multilinea

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

mi piacerebbe catturare due cose: la parte 'some_Varying_TEXT', e tutte le righe di testo maiuscolo che arriva due righe di sotto di essa in una cattura (posso rimuovere i caratteri di nuova riga in seguito). Ho provato con alcuni approcci:

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

e un sacco di variazioni del presente documento senza fortuna. L'ultimo sembra corrispondere le righe del testo una per una, il che non è quello che voglio veramente. Posso prendere la prima parte, nessun problema, ma non riesco a percepire le 4-5 righe di testo maiuscolo. Mi piacerebbe che match.group (1) sia un numero _ Variabile _ Testo e gruppo (2) da linea1 + riga2 + riga3 + ecc. Fino a quando non si incontra la riga vuota.

Se qualcuno è curioso, si suppone che sia una sequenza di amminoacidi che costituiscono una proteina.

+0

C'è qualcos'altro nel file oltre alla prima riga e al testo in maiuscolo? Non sono sicuro del perché dovresti usare un'espressione regolare invece di dividere tutto il testo in caratteri di nuova riga e prendere il primo elemento come "some_Varying_TEXT". – UncleZeiv

+2

sì, l'espressione regolare è lo strumento sbagliato per questo. – hop

+0

Il testo di esempio non ha un carattere '>'. Dovrebbe? – MiniQuark

risposta

81

Prova questo:

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

Credo che il problema più grande è che vi aspettate le ^ e $ ancore per abbinare linefeeds, ma non lo fanno. In modalità multilinea, ^ corrisponde immediatamente alla posizione successiva a a newline e $ corrisponde alla posizione immediatamente precedente a a newline.

Si noti inoltre che una nuova riga può essere costituita da un avanzamento riga (\ n), un ritorno a capo (\ r) o un ritorno a capo + avanzamento riga (\ r \ n). Se non si è certi che il vostro testo di arrivo utilizza solo linefeeds, è necessario utilizzare questa versione più inclusiva della regex:

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

BTW, non si vuole utilizzare il modificatore DOTALL qui; stai facendo affidamento sul fatto che il punto corrisponde a tutto eccetto newlines.

+0

Si consiglia di sostituire il secondo punto dell'espressione regolare con [A-Z] se non si desidera che questa espressione regolare corrisponda praticamente a qualsiasi file di testo con una seconda riga vuota. ;-) – MiniQuark

+0

La mia impressione è che i file di destinazione siano conformi a uno schema definito (e ripetitivo) di linee vuote e non vuote, quindi non dovrebbe essere necessario specificare [AZ], ma probabilmente non farà male , o. –

+0

Questa soluzione ha funzionato magnificamente. Per inciso, mi scuso, poiché ovviamente non ho chiarito abbastanza la situazione (e anche per il ritardo di questa risposta). Grazie per l'aiuto! – Jan

1

ritrovamento:

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

\ 1 = some_varying_text

\ 2 = linee di tutte le CAPS

Modifica (la prova che questo funziona):

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

Sembra sbagliato per me. Hai provato questo? – Triptych

+0

Sì, ho aggiunto del codice per te. –

+0

Sfortunatamente, questa espressione regolare corrisponderà anche a gruppi di lettere maiuscole separate da linee vuote. Potrebbe non essere un grosso problema però. – MiniQuark

14

Questo funzionerà:

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

Alcuni spiegazione su questa espressione regolare potrebbe essere utile: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Il primo carattere (^) significa "a partire dall'inizio di una linea". Si noti che non corrisponde alla nuova riga stessa (stessa cosa per $: significa "prima di una nuova riga", ma non corrisponde alla nuova riga stessa).
  • Quindi (.+?)\n\n significa "combina il minor numero di caratteri possibile (tutti i caratteri sono consentiti) fino a raggiungere due nuove linee". Il risultato (senza le nuove righe) viene inserito nel primo gruppo.
  • [A-Z]+\n significa "partita il maggior numero di lettere maiuscole come possibile fino a raggiungere una nuova riga. Questo definisce quello che chiamerò un TextLine.
  • ((?:TextLine)+) significa partita uno o più oggetti TextLine ma non mettere ogni riga in un gruppo. Invece, mettere tutto il oggetti TextLine in un gruppo.
  • si potrebbe aggiungere una finale \n nell'espressione regolare se si desidera imporre un doppio ritorno a capo alla fine.
  • Inoltre, se non siete sicuri su quale tipo di nuova linea si otterrà (\n o \r o \r\n) poi basta fissare l'espressione regolare sostituendo ogni occorrenza di \n da (?:\n|\r\n?).
+0

match() restituisce solo una corrispondenza, proprio all'inizio del testo di destinazione, ma l'OP ha detto che ci sarebbero centinaia di partite per file. Penso che tu vorresti finditer() invece. –

+1

@Alan: appena risolto, grazie. – MiniQuark

1

La mia preferenza.

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) 

A questo punto avete someVaryingText come una stringa, e gli acidi come una lista di stringhe. È possibile eseguire "".join(acids) per creare una singola stringa.

Trovo che questo sia meno frustrante (e più flessibili) di multilinea regex.

4

Se ogni file ha una sola sequenza di aminoacidi, non vorrei usare le espressioni regolari a tutti. Proprio qualcosa del genere:

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

Definitivamente il modo più semplice se ce n'era uno solo, ed è anche lavorabile con altro, se viene aggiunta qualche logica in più. Ci sono circa 885 proteine ​​in questo set di dati specifico, e ho sentito che una regex dovrebbe essere in grado di gestirlo. – Jan

Problemi correlati