Regulärer Ausdruck, der einem mehrzeiligen Textblock entspricht

104

Ich habe ein bisschen Probleme damit, dass ein Python-Regex funktioniert, wenn er mit Text verglichen wird, der mehrere Zeilen umfasst. Der Beispieltext lautet ('\ n' ist eine neue Zeile)

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

Ich möchte zwei Dinge erfassen: den Teil 'some_Varying_TEXT' und alle Zeilen in Großbuchstaben, die in einer Erfassung zwei Zeilen darunter stehen (ich kann die Zeilenumbrüche später entfernen). Ich habe es mit ein paar Ansätzen versucht:

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

und viele Variationen davon ohne Glück. Der letzte scheint nacheinander mit den Textzeilen übereinzustimmen, was ich nicht wirklich will. Ich kann den ersten Teil abfangen, kein Problem, aber ich kann die 4-5 Zeilen Großbuchstaben nicht abfangen. Ich möchte, dass match.group (1) some_Varying_Text und group (2) line1 + line2 + line3 + usw. ist, bis die leere Zeile gefunden wird.

Wenn jemand neugierig ist, soll es sich um eine Sequenz von Aminosäuren handeln, aus denen ein Protein besteht.

Jan.
quelle
Enthält die Datei neben der ersten Zeile und dem Großbuchstaben noch etwas anderes? Ich bin mir nicht sicher, warum Sie einen regulären Ausdruck verwenden würden, anstatt den gesamten Text in Zeilenumbrüche aufzuteilen und das erste Element als "some_Varying_TEXT" zu verwenden.
Onkel Zeiv
2
Ja, Regex sind das falsche Werkzeug dafür.
Ihr Beispieltext hat keinen führenden >Charakter. Sollte es?
MiniQuark

Antworten:

113

Versuche dies:

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

Ich denke, Ihr größtes Problem ist, dass Sie erwarten, dass die ^und $Anker mit den Zeilenvorschüben übereinstimmen, aber das tun sie nicht. Entspricht im mehrzeiligen Modus ^der Position unmittelbar nach einer neuen Zeile und $der Position unmittelbar vor einer neuen Zeile.

Beachten Sie auch, dass eine neue Zeile aus einem Zeilenvorschub (\ n), einem Wagenrücklauf (\ r) oder einem Wagenrücklauf + Zeilenvorschub (\ r \ n) bestehen kann. Wenn Sie nicht sicher sind, ob Ihr Zieltext nur Zeilenvorschübe verwendet, sollten Sie diese umfassendere Version des regulären Ausdrucks verwenden:

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

Übrigens möchten Sie den DOTALL-Modifikator hier nicht verwenden. Sie verlassen sich auf die Tatsache, dass der Punkt mit allen Zeilen außer Zeilenumbrüchen übereinstimmt.

Alan Moore
quelle
Möglicherweise möchten Sie den zweiten Punkt in der Regex durch [AZ] ersetzen, wenn dieser reguläre Ausdruck nicht mit nahezu jeder Textdatei mit einer leeren zweiten Zeile übereinstimmen soll. ;-)
MiniQuark
Mein Eindruck ist, dass die Zieldateien einem bestimmten (und sich wiederholenden) Muster aus leeren und nicht leeren Zeilen entsprechen. Daher sollte es nicht erforderlich sein, [AZ] anzugeben, aber es wird wahrscheinlich auch nicht schaden.
Alan Moore
Diese Lösung hat wunderbar funktioniert. Nebenbei entschuldige ich mich, da ich die Situation offensichtlich nicht genug geklärt habe (und auch für die Verspätung dieser Antwort). Danke für Ihre Hilfe!
Januar
21

Das wird funktionieren:

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

Einige Erklärungen zu diesem regulären Ausdruck könnten nützlich sein: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Das erste Zeichen ( ^) bedeutet "am Zeilenanfang beginnen". Beachten Sie, dass es nicht mit der Newline selbst übereinstimmt (dasselbe gilt für $: Es bedeutet "kurz vor einer Newline", aber es stimmt nicht mit der Newline selbst überein).
  • (.+?)\n\nBedeutet dann "so wenig Zeichen wie möglich abgleichen (alle Zeichen sind zulässig), bis Sie zwei Zeilenumbrüche erreichen". Das Ergebnis (ohne die Zeilenumbrüche) wird in die erste Gruppe eingefügt.
  • [A-Z]+\nbedeutet "Ordne so viele Großbuchstaben wie möglich zu, bis du eine neue Zeile erreichst. Dies definiert, was ich eine Textzeile nennen werde .
  • ((?:Textzeile)+) bedeutet, dass eine oder mehrere Textzeilen übereinstimmen, aber nicht jede Zeile einer Gruppe zugeordnet wird. Stattdessen setzen alle die Textzeilen in einer Gruppe.
  • Sie können \ndem regulären Ausdruck ein Finale hinzufügen, wenn Sie am Ende einen doppelten Zeilenumbruch erzwingen möchten.
  • Wenn Sie sich nicht sicher sind, welche Art von Zeilenumbruch Sie erhalten ( \noder \roder \r\n), korrigieren Sie einfach den regulären Ausdruck, indem Sie jedes Vorkommen von \nby ersetzen (?:\n|\r\n?).
MiniQuark
quelle
1
match () gibt nur eine Übereinstimmung ganz am Anfang des Zieltextes zurück, aber das OP sagte, dass es Hunderte von Übereinstimmungen pro Datei geben würde. Ich denke, Sie möchten stattdessen finditer ().
Alan Moore
6

Wenn jede Datei nur eine Sequenz von Aminosäuren enthält, würde ich überhaupt keine regulären Ausdrücke verwenden. Nur so etwas:

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
MiniQuark
quelle
Auf jeden Fall der einfachste Weg, wenn es nur einen gab, und es ist auch mit mehr möglich, wenn etwas mehr Logik hinzugefügt wird. Es gibt jedoch ungefähr 885 Proteine ​​in diesem spezifischen Datensatz, und ich war der Meinung, dass ein Regex in der Lage sein sollte, damit umzugehen.
Januar
4

finden:

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

\ 1 = etwas variierender Text

\ 2 = Zeilen aller CAPS

Bearbeiten (Beweis, dass dies funktioniert):

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])
Jason Coon
quelle
Leider stimmt dieser reguläre Ausdruck auch mit Gruppen von Großbuchstaben überein, die durch Leerzeilen getrennt sind. Es könnte jedoch keine große Sache sein.
MiniQuark
Sieht aus wie coonj FASTA-Dateien mag. ;)
Andrew Dalke
4

Das Folgende ist ein regulärer Ausdruck, der einem mehrzeiligen Textblock entspricht:

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

Meine Vorliebe.

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 )

Zu diesem Zeitpunkt haben Sie someVaryingText als Zeichenfolge und die Säuren als Liste von Zeichenfolgen. Sie können "".join( acids )eine einzelne Zeichenfolge erstellen.

Ich finde das weniger frustrierend (und flexibler) als mehrzeilige Regexe.

S.Lott
quelle