Expression régulière correspondant à un bloc de texte multiligne


105

J'ai un peu de mal à faire fonctionner une expression régulière Python lors de la correspondance avec du texte qui s'étend sur plusieurs lignes. L'exemple de texte est ('\ n' est une nouvelle ligne)

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

Je voudrais capturer deux choses: la partie 'some_Varying_TEXT' et toutes les lignes de texte majuscules qui viennent deux lignes en dessous dans une capture (je peux supprimer les caractères de nouvelle ligne plus tard). J'ai essayé avec quelques approches:

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

et de nombreuses variantes sans chance. Le dernier semble correspondre aux lignes de texte une par une, ce qui n'est pas ce que je veux vraiment. Je peux attraper la première partie, pas de problème, mais je n'arrive pas à attraper les 4-5 lignes de texte en majuscules. Je voudrais que match.group (1) soit some_Varying_Text et group (2) soit line1 + line2 + line3 + etc jusqu'à ce que la ligne vide soit rencontrée.

Si quelqu'un est curieux, c'est censé être une séquence d'acides aminés qui composent une protéine.


Y a-t-il autre chose dans le fichier en plus de la première ligne et du texte en majuscules? Je ne sais pas pourquoi vous utiliseriez une regex au lieu de diviser tout le texte en caractères de nouvelle ligne et de prendre le premier élément comme "some_Varying_TEXT".
UncleZeiv

2
oui, les regex sont le mauvais outil pour cela.

Votre exemple de texte n'a pas de >caractère principal . Devrait-il?
MiniQuark

Réponses:


114

Essaye ça:

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

Je pense que votre plus gros problème est que vous vous attendez à ce que les ancres ^et $correspondent aux sauts de ligne, mais ce n'est pas le cas. En mode multiligne, ^correspond à la position immédiatement après une nouvelle ligne et $correspond à la position précédant immédiatement une nouvelle ligne.

Sachez également qu'une nouvelle ligne peut être constituée d'un saut de ligne (\ n), d'un retour chariot (\ r) ou d'un retour chariot + saut de ligne (\ r \ n). Si vous n'êtes pas certain que votre texte cible n'utilise que des sauts de ligne, vous devez utiliser cette version plus inclusive de l'expression régulière:

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

BTW, vous ne voulez pas utiliser le modificateur DOTALL ici; vous vous fiez au fait que le point correspond à tout sauf aux retours à la ligne.


Vous pouvez remplacer le deuxième point de l'expression régulière par [AZ] si vous ne voulez pas que cette expression régulière corresponde à n'importe quel fichier texte avec une deuxième ligne vide. ;-)
MiniQuark

Mon impression est que les fichiers cibles seront conformes à un modèle défini (et répétitif) de lignes vides ou non vides, donc il ne devrait pas être nécessaire de spécifier [AZ], mais cela ne fera probablement pas de mal non plus.
Alan Moore

Cette solution a fonctionné à merveille. En passant, je m'excuse, car je n'ai évidemment pas suffisamment clarifié la situation (et aussi pour le retard de cette réponse). Merci de votre aide!
janvier

21

Cela fonctionnera:

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

Quelques explications sur cette expression régulière peuvent être utiles: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Le premier caractère ( ^) signifie "commençant au début d'une ligne". Sachez qu'il ne correspond pas à la nouvelle ligne elle-même (même chose pour $: cela signifie "juste avant une nouvelle ligne", mais cela ne correspond pas à la nouvelle ligne elle-même).
  • Alors (.+?)\n\nsignifie "correspondre le moins de caractères possible (tous les caractères sont autorisés) jusqu'à ce que vous atteigniez deux nouvelles lignes". Le résultat (sans les nouvelles lignes) est placé dans le premier groupe.
  • [A-Z]+\nsignifie "correspond à autant de lettres majuscules que possible jusqu'à ce que vous atteigniez une nouvelle ligne. Ceci définit ce que j'appellerai une ligne de texte .
  • ((?:textline)+) signifie correspondre à une ou plusieurs lignes de texte mais ne pas mettre chaque ligne dans un groupe. Au lieu de cela, mettez toutes les lignes de texte dans un groupe.
  • Vous pouvez ajouter une finale \ndans l'expression régulière si vous souhaitez appliquer un double saut de ligne à la fin.
  • De plus, si vous n'êtes pas sûr du type de nouvelle ligne que vous obtiendrez ( \nou \rou \r\n), corrigez simplement l'expression régulière en remplaçant chaque occurrence de \npar (?:\n|\r\n?).

1
match () ne renvoie qu'une seule correspondance, au tout début du texte cible, mais l'OP a dit qu'il y aurait des centaines de correspondances par fichier. Je pense que vous voudriez finditer () à la place.
Alan Moore

6

Si chaque fichier ne contient qu'une seule séquence d'acides aminés, je n'utiliserais pas du tout d'expressions régulières. Juste quelque chose comme ça:

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

Certainement le moyen le plus simple s'il n'y en avait qu'un, et il est également utilisable avec plus, si un peu plus de logique est ajoutée. Il y a cependant environ 885 protéines dans cet ensemble de données spécifique, et j'ai senti qu'une regex devrait être capable de gérer cela.
janvier

4

trouver:

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

\ 1 = some_varying_text

\ 2 = lignes de tous les CAPS

Edit (preuve que cela fonctionne):

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

Malheureusement, cette expression régulière correspondra également à des groupes de majuscules séparés par des lignes vides. Ce n'est peut-être pas un gros problème.
MiniQuark

On dirait que coonj aime les fichiers FASTA. ;)
Andrew Dalke

4

Voici une expression régulière correspondant à un bloc de texte multiligne:

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

1

Ma préférence.

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 )

À ce stade, vous avez someVaryingText sous forme de chaîne et les acides sous forme de liste de chaînes. Vous pouvez "".join( acids )faire une seule chaîne.

Je trouve cela moins frustrant (et plus flexible) que les expressions rationnelles multilignes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.