Espressione regolare che corrisponde a un blocco di testo multilinea


105

Ho qualche problema a far funzionare una regex Python durante la corrispondenza con un testo che si estende su più righe. Il testo di esempio è ('\ n' è una nuova riga)

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

Vorrei catturare due cose: la parte "some_Varying_TEXT" e tutte le righe di testo maiuscolo che vengono due righe sotto di essa in un'unica acquisizione (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 molte varianti di questo senza fortuna. L'ultimo sembra abbinare le righe di testo una per una, il che non è quello che voglio veramente. Riesco a prendere la prima parte, nessun problema, ma non riesco a catturare le 4-5 righe di testo maiuscolo. Vorrei che match.group (1) fosse some_Varying_Text e group (2) fosse line1 + line2 + line3 + ecc. Finché non si incontra la riga vuota.

Se qualcuno è curioso, dovrebbe essere una sequenza di aminoacidi che compongono una proteina.


C'è qualcos'altro nel file oltre alla prima riga e al testo in maiuscolo? Non sono sicuro del motivo per cui dovresti usare una regex invece di dividere tutto il testo in caratteri di nuova riga e prendere il primo elemento come "some_Varying_TEXT".
UncleZeiv

2
sì, le espressioni regolari sono lo strumento sbagliato per questo.

Il tuo testo di esempio non ha un >carattere principale . Dovrebbe?
MiniQuark

Risposte:


114

Prova questo:

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

Penso che il tuo problema più grande sia che ti aspetti che le ancore ^e $corrispondano agli avanzamenti di riga, ma non lo fanno. In modalità multilinea, ^corrisponde alla posizione immediatamente successiva a una nuova riga e $alla posizione immediatamente precedente a una nuova riga.

Tieni presente anche 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 sei sicuro che il testo di destinazione utilizzi solo avanzamenti riga, dovresti utilizzare questa versione più inclusiva della regex:

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

BTW, non vuoi usare il modificatore DOTALL qui; ti affidi al fatto che il punto corrisponde a tutto tranne le nuove righe.


Potresti voler sostituire il secondo punto nella regex con [AZ] se non vuoi che questa espressione regolare corrisponda a qualsiasi file di testo con una seconda riga vuota. ;-)
MiniQuark

La mia impressione è che i file di destinazione si conformeranno a uno schema definito (e ripetitivo) di righe vuote e non vuote, quindi non dovrebbe essere necessario specificare [AZ], ma probabilmente non farà male neanche.
Alan Moore,

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

21

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

Alcune spiegazioni su questa espressione regolare potrebbero essere utili: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Il primo carattere ( ^) significa "iniziare dall'inizio di una riga". Tieni presente che non corrisponde alla nuova riga stessa (lo stesso per $: significa "appena prima di una nuova riga", ma non corrisponde alla nuova riga stessa).
  • Allora (.+?)\n\nsignifica "abbina il minor numero di caratteri possibile (tutti i caratteri sono consentiti) fino a raggiungere due nuove righe". Il risultato (senza le nuove righe) viene inserito nel primo gruppo.
  • [A-Z]+\nsignifica "abbina il maggior numero possibile di lettere maiuscole fino a raggiungere una nuova riga. Questo definisce ciò che chiamerò una riga di testo .
  • ((?:textline)+) significa abbinare una o più textline ma non mettere ogni riga in un gruppo. Invece, metti tutte le linee di testo in un gruppo.
  • Potresti aggiungere una finale \nnell'espressione regolare se vuoi applicare un doppio ritorno a capo alla fine.
  • Inoltre, se non sei sicuro del tipo di nuova riga che otterrai ( \no \ro \r\n), correggi l'espressione regolare sostituendo ogni occorrenza di \nby (?:\n|\r\n?).

1
match () restituisce solo una corrispondenza, all'inizio del testo di destinazione, ma l'OP ha detto che ci sarebbero state centinaia di corrispondenze per file. Penso che vorresti invece finditer ().
Alan Moore,

6

Se ogni file ha solo una sequenza di aminoacidi, non userei affatto le espressioni regolari. 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

Sicuramente il modo più semplice se ce ne fosse uno solo, ed è anche utilizzabile con più, se viene aggiunta un po 'più di logica. Tuttavia, ci sono circa 885 proteine ​​in questo specifico set di dati e ho sentito che una regex dovrebbe essere in grado di gestirlo.
gennaio

4

trova:

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

\ 1 = some_varying_text

\ 2 = righe di tutte le maiuscole

Modifica (prova che 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])

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

Sembra che a coonj piacciano i file FASTA. ;)
Andrew Dalke,

4

La seguente è un'espressione regolare che corrisponde a un blocco di testo su più righe:

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

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 hai someVaryingText come stringa e gli acidi come elenco di stringhe. Puoi fare "".join( acids )per creare una singola stringa.

Lo trovo meno frustrante (e più flessibile) delle espressioni regolari multilinea.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.