2009-02-25 11 views
60

J'ai un peu de mal à faire fonctionner une regex Python lors de la comparaison avec du texte qui s'étend sur plusieurs lignes. Le texte exemple est (« \ n » est un saut de ligne)Expression régulière correspondant à un bloc de texte multiligne

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 saisir deux choses: la partie « some_Varying_TEXT », et toutes les lignes de texte en majuscules qui vient deux lignes ci-dessous dans une capture (je peux enlever 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 beaucoup de variations des présentes sans chance. Le dernier semble correspondre un à un aux lignes de texte, 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. J'aimerais que match.group (1) soit _ Variable _ Le texte et le groupe (2) doivent être ligne1 + ligne2 + ligne3 + etc jusqu'à ce que la ligne vide soit rencontrée.

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

+0

Y at-il quelque chose d'autre dans le fichier en dehors de la première ligne et du texte en majuscule? Je ne sais pas pourquoi vous utiliseriez une regex au lieu de diviser tout le texte en caractères de nouvelle ligne et en prenant le premier élément comme "some_Varying_TEXT". – UncleZeiv

+2

oui, regex sont le mauvais outil pour cela. – hop

+0

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

Répondre

81

Essayez ceci:

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

Je pense que votre plus gros problème est que vous vous attendez les ancres ^ et $ pour correspondre linefeeds, mais ils ne le font pas. En mode multiligne, ^ correspond immédiatement à la position suivant une nouvelle ligne et $ correspond immédiatement à la position précédant 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 utilise seulement linefeeds, vous devez utiliser cette version plus inclusive de la regex:

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

BTW, vous ne voulez pas utiliser ici le modificateur dotall; vous comptez sur le fait que le point correspond à tout sauf newlines.

+0

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

+0

Mon impression est que les fichiers cibles seront conformes à un schéma défini (et répétitif) de lignes vides par rapport aux lignes non vides, donc il ne devrait pas être nécessaire de spécifier [AZ], mais cela ne fera probablement pas de mal , non plus. –

+0

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

1

trouver:

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

\ 1 = some_varying_text

\ 2 = lignes de MAJUSCULES

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

Ça me semble faux. Avez-vous testé cela? – Triptych

+0

C'est le cas, j'ai ajouté du code pour vous. –

+0

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

14

Cette 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 à propos de cette expression régulière pourrait être utile: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Le premier caractère (^) signifie "à partir du 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 un saut de ligne", mais cela ne correspond pas à la nouvelle ligne elle-même).
  • Ensuite, (.+?)\n\n signifie "correspondre aussi peu de caractères que 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]+\n signifie « correspondre autant de lettres majuscules possible jusqu'à un retour à la ligne. Cela définit ce que je vais appeler un TextLine.
  • ((?:TextLine)+) signifie apparier un ou plusieurs textlines mais ne mettre chaque ligne dans un groupe. au lieu de cela, mettre tous le textlines dans un groupe.
  • vous pouvez ajouter une \n finale dans l'expression régulière si vous souhaitez appliquer un double saut de ligne à la fin.
  • En outre, si vous n'êtes pas sûr de ce type de saut de ligne, vous obtiendrez (\n ou \r ou \r\n) puis juste fixer l'expression régulière en remplaçant chaque occurrence de \n par (?:\n|\r\n?).
+0

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

+1

@Alan: Juste corrigé, merci. – MiniQuark

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 comme une chaîne, et les acides comme une liste de chaînes. Vous pouvez faire "".join(acids) pour créer une chaîne unique. Je trouve cela moins frustrant (et plus flexible) que les expressions rationnelles multilignes.

4

Si chaque fichier ne contient qu'une seule séquence d'acides aminés, je n'utiliserais aucune expression régulière. Juste quelque chose comme ceci:

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

Définitivement le moyen le plus simple s'il n'y en avait qu'un, et il est aussi possible de le faire avec plus, si un peu plus de logique est ajouté. Il y a environ 885 protéines dans ce jeu de données spécifique, et j'ai pensé qu'une regex devrait être capable de gérer cela. – Jan