Ekspresi reguler yang cocok dengan blok teks multiline

105

Saya mengalami sedikit kesulitan membuat Python regex berfungsi saat mencocokkan dengan teks yang mencakup banyak baris. Contoh teksnya adalah ('\ n' adalah baris baru)

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

Saya ingin menangkap dua hal: bagian 'some_Varying_TEXT', dan semua baris teks huruf besar yang muncul dua baris di bawahnya dalam satu tangkapan (saya bisa menghapus karakter baris baru nanti). Saya sudah mencoba dengan beberapa pendekatan:

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

dan banyak variasinya tidak berhasil. Yang terakhir sepertinya cocok dengan baris teks satu per satu, yang sebenarnya bukan yang saya inginkan. Saya bisa menangkap bagian pertama, tidak masalah, tapi sepertinya saya tidak bisa menangkap 4-5 baris teks huruf besar. Saya ingin match.group (1) menjadi some_Varying_Text dan group (2) menjadi baris1 + baris2 + baris3 + dll sampai baris kosong ditemukan.

Jika ada yang penasaran, itu seharusnya urutan asam amino yang membentuk protein.

Jan
sumber
Apakah ada hal lain di file selain baris pertama dan teks huruf besar? Saya tidak yakin mengapa Anda menggunakan ekspresi reguler daripada memisahkan semua teks pada karakter baris baru dan mengambil elemen pertama sebagai "some_Varying_TEXT".
UncleZeiv
2
ya, regex adalah alat yang salah untuk ini.
Contoh teks Anda tidak memiliki >karakter utama . Haruskah itu?
MiniQuark

Jawaban:

114

Coba ini:

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

Saya pikir masalah terbesar Anda adalah Anda mengharapkan ^dan $anchor cocok dengan linefeeds, tetapi ternyata tidak. Dalam mode multiline, ^cocokkan posisi tepat setelah baris baru dan $cocok dengan posisi tepat sebelum baris baru.

Berhati-hatilah juga, bahwa baris baru dapat terdiri dari satu baris (\ n), carriage-return (\ r), atau carriage-return + linefeed (\ r \ n). Jika Anda tidak yakin bahwa teks target Anda hanya menggunakan umpan baris, Anda harus menggunakan versi regex yang lebih inklusif ini:

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

BTW, Anda tidak ingin menggunakan pengubah DOTALL di sini; Anda mengandalkan fakta bahwa titik tersebut cocok dengan segala sesuatu kecuali baris baru.

Alan Moore
sumber
Anda mungkin ingin mengganti titik kedua dalam ekspresi reguler dengan [AZ] jika Anda tidak ingin ekspresi reguler ini cocok dengan hampir semua file teks dengan baris kedua kosong. ;-)
MiniQuark
Kesan saya adalah bahwa file target akan sesuai dengan pola pasti (dan berulang) dari baris kosong vs. tidak kosong, jadi tidak perlu untuk menentukan [AZ], tapi mungkin juga tidak akan merugikan.
Alan Moore
Solusi ini bekerja dengan baik. Sebagai tambahan, saya minta maaf, karena saya jelas tidak cukup menjelaskan situasinya (dan juga atas keterlambatan jawaban ini). Terima kasih atas bantuan Anda!
Jan
21

Ini akan berhasil:

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

Beberapa penjelasan tentang ekspresi reguler ini mungkin berguna: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • Karakter pertama ( ^) berarti "dimulai dari awal baris". Ketahuilah bahwa ini tidak cocok dengan baris baru itu sendiri (sama untuk $: artinya "tepat sebelum baris baru", tetapi tidak cocok dengan baris baru itu sendiri).
  • Kemudian (.+?)\n\nberarti "cocokkan sesedikit mungkin karakter (semua karakter diperbolehkan) sampai Anda mencapai dua baris baru". Hasilnya (tanpa baris baru) dimasukkan ke dalam kelompok pertama.
  • [A-Z]+\nberarti "cocokkan sebanyak mungkin huruf besar sampai Anda mencapai baris baru. Ini mendefinisikan apa yang akan saya sebut baris teks .
  • ((?:textline)+) berarti mencocokkan satu atau lebih baris teks tetapi tidak menempatkan setiap baris dalam satu kelompok. Sebaliknya, letakkan semua baris teks dalam satu grup.
  • Anda dapat menambahkan final \ndalam ekspresi reguler jika Anda ingin menerapkan baris baru ganda di akhir.
  • Juga, jika Anda tidak yakin tentang tipe baris baru apa yang akan Anda dapatkan ( \natau \ratau \r\n) maka perbaiki ekspresi reguler dengan mengganti setiap kemunculan \noleh (?:\n|\r\n?).
MiniQuark
sumber
1
match () hanya mengembalikan satu kecocokan, di awal teks target, tetapi OP mengatakan akan ada ratusan kecocokan per file. Saya pikir Anda akan menginginkan finditer () sebagai gantinya.
Alan Moore
6

Jika setiap file hanya memiliki satu urutan asam amino, saya tidak akan menggunakan ekspresi reguler sama sekali. Sesuatu seperti ini:

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
sumber
Jelas cara termudah jika hanya ada satu, dan itu juga bisa diterapkan dengan lebih, jika lebih banyak logika ditambahkan. Ada sekitar 885 protein dalam kumpulan data khusus ini, dan saya merasa bahwa regex seharusnya dapat menangani ini.
Jan
4

Temukan:

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

\ 1 = some_varying_text

\ 2 = baris dari semua CAPS

Edit (bukti bahwa ini berfungsi):

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
sumber
Sayangnya, ekspresi reguler ini juga akan cocok dengan kelompok huruf kapital yang dipisahkan oleh baris kosong. Ini mungkin bukan masalah besar.
MiniQuark
Sepertinya coonj menyukai file FASTA. ;)
Andrew Dalke
4

Berikut ini adalah ekspresi reguler yang cocok dengan blok teks multiline:

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

Preferensi saya.

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 )

Pada titik ini Anda memiliki someVaryingText sebagai string, dan asam sebagai daftar string. Anda bisa melakukannya "".join( acids )dengan membuat satu senar.

Saya merasa ini kurang membuat frustrasi (dan lebih fleksibel) daripada multiline regex.

S. Lott
sumber