Expresión regular que coincide con un bloque de texto de varias líneas


105

Tengo un poco de problemas para hacer que una expresión regular de Python funcione cuando se compara con texto que abarca varias líneas. El texto de ejemplo es ('\ n' es una nueva línea)

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

Me gustaría capturar dos cosas: la parte 'some_Varying_TEXT' y todas las líneas de texto en mayúsculas que vienen dos líneas debajo en una captura (puedo eliminar los caracteres de nueva línea más adelante). Lo intenté con algunos enfoques:

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

y muchas variaciones del mismo sin suerte. El último parece coincidir con las líneas de texto una por una, que no es lo que realmente quiero. Puedo captar la primera parte, no hay problema, pero parece que no puedo captar las 4-5 líneas de texto en mayúsculas. Me gustaría que match.group (1) sea some_Varying_Text y group (2) sea line1 + line2 + line3 + etc hasta que se encuentre la línea vacía.

Si alguien tiene curiosidad, se supone que es una secuencia de aminoácidos que forman una proteína.


¿Hay algo más en el archivo además de la primera línea y el texto en mayúsculas? No estoy seguro de por qué usaría una expresión regular en lugar de dividir todo el texto en caracteres de nueva línea y tomar el primer elemento como "some_Varying_TEXT".
UncleZeiv

2
sí, las expresiones regulares son la herramienta incorrecta para esto.

Su texto de muestra no tiene un >carácter principal . ¿Deberia?
MiniQuark

Respuestas:


114

Prueba esto:

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

Creo que su mayor problema es que usted está esperando el ^y $anclajes para que coincida con los avances de línea, pero no lo hacen. En el modo multilínea, ^coincide con la posición inmediatamente siguiente a una nueva línea y $coincide con la posición inmediatamente anterior a una nueva línea.

También tenga en cuenta que una nueva línea puede consistir en un salto de línea (\ n), un retorno de carro (\ r) o un retorno de carro + salto de línea (\ r \ n). Si no está seguro de que su texto de destino use solo avances de línea, debe usar esta versión más inclusiva de la expresión regular:

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

Por cierto, no quieres usar el modificador DOTALL aquí; confía en el hecho de que el punto coincide con todo excepto con las nuevas líneas.


Es posible que desee reemplazar el segundo punto en la expresión regular por [AZ] si no desea que esta expresión regular coincida con casi cualquier archivo de texto con una segunda línea vacía. ;-)
MiniQuark

Mi impresión es que los archivos de destino se ajustarán a un patrón definido (y repetido) de líneas vacías frente a no vacías, por lo que no debería ser necesario especificar [AZ], pero probablemente tampoco hará daño.
Alan Moore

Esta solución funcionó a la perfección. Aparte, me disculpo, ya que obviamente no aclaré la situación lo suficiente (y también por la tardanza de esta respuesta). ¡Gracias por tu ayuda!
enero

21

Esto funcionará:

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

Alguna explicación sobre esta expresión regular puede ser útil: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • El primer carácter ( ^) significa "comenzando al principio de una línea". Tenga en cuenta que no coincide con la nueva línea en sí (lo mismo para $: significa "justo antes de una nueva línea", pero no coincide con la nueva línea).
  • Luego (.+?)\n\nsignifica "hacer coincidir la menor cantidad de caracteres posible (se permiten todos los caracteres) hasta llegar a dos nuevas líneas". El resultado (sin las nuevas líneas) se coloca en el primer grupo.
  • [A-Z]+\nsignifica "coincidir con tantas letras mayúsculas como sea posible hasta llegar a una nueva línea. Esto define lo que llamaré una línea de texto" .
  • ((?:línea de texto)+) significa hacer coincidir una o más líneas de texto pero no poner cada línea en un grupo. En su lugar, coloque todas las líneas de texto en un grupo.
  • Puede agregar un final \nen la expresión regular si desea aplicar un salto de línea doble al final.
  • Además, si no está seguro del tipo de nueva línea que obtendrá ( \no \ro \r\n), simplemente corrija la expresión regular reemplazando cada aparición de \npor (?:\n|\r\n?).

1
match () solo devuelve una coincidencia, al principio del texto de destino, pero el OP dijo que habría cientos de coincidencias por archivo. Creo que querrías finditer () en su lugar.
Alan Moore

6

Si cada archivo solo tiene una secuencia de aminoácidos, no usaría expresiones regulares en absoluto. Algo como esto:

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

Definitivamente la forma más fácil si solo hubiera una, y también funciona con más, si se agrega algo más de lógica. Sin embargo, hay alrededor de 885 proteínas en este conjunto de datos específico, y sentí que una expresión regular debería poder manejar esto.
enero

4

encontrar:

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

\ 1 = algún_texto_variado

\ 2 = líneas de todas las MAYÚSCULAS

Editar (prueba de que esto funciona):

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

Desafortunadamente, esta expresión regular también coincidirá con grupos de letras mayúsculas separadas por líneas vacías. Sin embargo, puede que no sea gran cosa.
MiniQuark

Parece que a Coonj le gustan los archivos FASTA. ;)
Andrew Dalke

4

La siguiente es una expresión regular que coincide con un bloque de texto de varias líneas:

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

1

Mi preferencia.

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 )

En este punto, tiene someVaryingText como una cadena y los ácidos como una lista de cadenas. Puedes hacer "".join( acids )para hacer una sola cuerda.

Encuentro esto menos frustrante (y más flexible) que las expresiones regulares de varias líneas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.