นิพจน์ทั่วไปที่ตรงกับบล็อกข้อความหลายบรรทัด


108

ฉันมีปัญหาเล็กน้อยในการทำให้ Python regex ทำงานเมื่อจับคู่กับข้อความที่ครอบคลุมหลายบรรทัด ข้อความตัวอย่างคือ ('\ n' คือขึ้นบรรทัดใหม่)

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

ฉันต้องการจับภาพสองสิ่ง: ส่วน 'some_Varying_TEXT' และบรรทัดของข้อความตัวพิมพ์ใหญ่ทั้งหมดที่มีสองบรรทัดด้านล่างในการจับครั้งเดียว (ฉันสามารถตัดอักขระขึ้นบรรทัดใหม่ได้ในภายหลัง) ฉันได้ลองใช้หลายวิธี:

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

และรูปแบบต่างๆมากมายโดยไม่มีโชค ข้อความสุดท้ายดูเหมือนจะตรงกับบรรทัดของข้อความทีละบรรทัดซึ่งไม่ใช่สิ่งที่ฉันต้องการจริงๆ ฉันจับส่วนแรกได้ไม่มีปัญหา แต่ดูเหมือนจะจับข้อความตัวพิมพ์ใหญ่ 4-5 บรรทัดไม่ได้ ฉันต้องการให้ match.group (1) เป็น some_Varying_Text และ group (2) เป็น line1 + line2 + line3 + etc จนกว่าจะพบบรรทัดว่าง

ถ้าใครอยากรู้อยากเห็นควรเป็นลำดับของกรดอะมิโนที่ประกอบเป็นโปรตีน


มีสิ่งอื่นในไฟล์นอกเหนือจากบรรทัดแรกและข้อความตัวพิมพ์ใหญ่หรือไม่? ฉันไม่แน่ใจว่าทำไมคุณถึงใช้นิพจน์ทั่วไปแทนที่จะแยกข้อความทั้งหมดเป็นอักขระขึ้นบรรทัดใหม่และใช้องค์ประกอบแรกเป็น "some_Varying_TEXT"
UncleZeiv

2
ใช่ regex เป็นเครื่องมือที่ไม่ถูกต้องสำหรับสิ่งนี้

ข้อความตัวอย่างของคุณไม่มี>อักขระนำหน้า ควรเป็น?
MiniQuark

คำตอบ:


116

ลองสิ่งนี้:

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

ฉันคิดว่าปัญหาที่ใหญ่ที่สุดของคุณคือคุณคาดหวังว่า^และ$แองเคอร์จะตรงกับฟีดไลน์ แต่ไม่ ในโหมดหลายบรรทัดจับคู่^ตำแหน่งทันทีตามขึ้นบรรทัดใหม่และ$ตรงกับตำแหน่งที่อยู่ก่อนขึ้นบรรทัดใหม่ทันที

โปรดทราบด้วยว่าการขึ้นบรรทัดใหม่อาจประกอบด้วย linefeed (\ n), carriage-return (\ r) หรือ carriage-return + linefeed (\ r \ n) หากคุณไม่แน่ใจว่าข้อความเป้าหมายของคุณใช้เฉพาะบรรทัดฟีดคุณควรใช้ regex เวอร์ชันที่ครอบคลุมมากขึ้น:

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

BTW คุณไม่ต้องการใช้ตัวปรับแต่ง DOTALL ที่นี่ คุณอาศัยข้อเท็จจริงที่ว่าจุดนั้นตรงกับทุกอย่างยกเว้นการขึ้นบรรทัดใหม่


คุณอาจต้องการแทนที่จุดที่สองใน regex ด้วย [AZ] หากคุณไม่ต้องการให้นิพจน์ทั่วไปนี้จับคู่กับไฟล์ข้อความใด ๆ ที่มีบรรทัดที่สองว่างเปล่า ;-)
MiniQuark

ความประทับใจของฉันคือไฟล์เป้าหมายจะเป็นไปตามรูปแบบที่แน่นอน (และการทำซ้ำ) ของบรรทัดว่างกับบรรทัดที่ไม่ว่างดังนั้นจึงไม่จำเป็นต้องระบุ [AZ] แต่มันก็อาจจะไม่เสียหายเช่นกัน
Alan Moore

โซลูชันนี้ทำงานได้อย่างสวยงาม นอกจากนี้ฉันต้องขออภัยเนื่องจากเห็นได้ชัดว่าฉันไม่ได้ชี้แจงสถานการณ์เพียงพอ (และสำหรับความล่าช้าของการตอบกลับนี้) ขอบคุณสำหรับความช่วยเหลือของคุณ!
ม.ค.

24

สิ่งนี้จะได้ผล:

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

คำอธิบายบางอย่างเกี่ยวกับนิพจน์ทั่วไปนี้อาจเป็นประโยชน์: ^(.+?)\n\n((?:[A-Z]+\n)+)

  • อักขระตัวแรก (^ ) หมายถึง "เริ่มต้นที่จุดเริ่มต้นของบรรทัด" โปรดทราบว่ามันไม่ตรงกับการขึ้นบรรทัดใหม่ (เหมือนกันสำหรับ $: หมายถึง "ก่อนขึ้นบรรทัดใหม่" แต่ไม่ตรงกับการขึ้นบรรทัดใหม่เอง)
  • จากนั้น(.+?)\n\nหมายความว่า "จับคู่อักขระให้น้อยที่สุดเท่าที่จะเป็นไปได้ (อนุญาตให้ใช้อักขระทั้งหมด) จนกว่าจะถึงบรรทัดใหม่สองรายการ" ผลลัพธ์ (โดยไม่ขึ้นบรรทัดใหม่) จะถูกใส่ไว้ในกลุ่มแรก
  • [A-Z]+\nหมายถึง "การแข่งขันเป็นจำนวนมากตัวอักษรพิมพ์ใหญ่ที่เป็นไปได้จนกว่าจะถึงบรรทัดใหม่. กำหนดนี้สิ่งที่ฉันจะเรียกTEXTLINE
  • ((?:textline)+)หมายถึงการจับคู่บรรทัดข้อความอย่างน้อยหนึ่งบรรทัดแต่อย่าใส่แต่ละบรรทัดในกลุ่ม แทนที่จะใส่ทุกtextlinesในกลุ่มหนึ่ง
  • คุณสามารถเพิ่มขั้นสุดท้าย \nในนิพจน์ทั่วไปหากคุณต้องการบังคับใช้การขึ้นบรรทัดใหม่สองครั้งในตอนท้าย
  • นอกจากนี้ถ้าคุณไม่แน่ใจเกี่ยวกับสิ่งที่ประเภทของการขึ้นบรรทัดใหม่คุณจะได้รับ ( \nหรือ\rหรือ\r\n) แล้วเพียงแค่แก้ไขการแสดงออกปกติโดยการเปลี่ยนทุกการเกิดโดย\n(?:\n|\r\n?)

1
match () ส่งคืนการจับคู่เพียงรายการเดียวที่จุดเริ่มต้นของข้อความเป้าหมาย แต่ OP กล่าวว่าจะมีการจับคู่หลายร้อยรายการต่อไฟล์ ฉันคิดว่าคุณต้องการ finditer () แทน
Alan Moore

6

หากแต่ละไฟล์มีกรดอะมิโนเพียงลำดับเดียวฉันจะไม่ใช้นิพจน์ทั่วไปเลย สิ่งนี้:

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

เป็นวิธีที่ง่ายที่สุดอย่างแน่นอนหากมีเพียงวิธีเดียวและยังสามารถใช้งานได้มากขึ้นหากมีการเพิ่มตรรกะเพิ่มเติม แม้ว่าจะมีโปรตีนประมาณ 885 ชนิดในชุดข้อมูลนี้และฉันรู้สึกว่า regex ควรจะจัดการสิ่งนี้ได้
ม.ค.

4

หา:

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

\ 1 = some_varying_text

\ 2 = เส้นของ CAPS ทั้งหมด

แก้ไข (พิสูจน์ว่าใช้งานได้):

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

ขออภัยนิพจน์ทั่วไปนี้จะจับคู่กลุ่มของตัวพิมพ์ใหญ่ที่คั่นด้วยบรรทัดว่าง มันอาจจะไม่ใช่เรื่องใหญ่
MiniQuark

ดูเหมือนว่า coonj จะชอบไฟล์ FASTA ;)
Andrew Dalke

4

ต่อไปนี้เป็นนิพจน์ทั่วไปที่ตรงกับบล็อกข้อความหลายบรรทัด:

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

1

ความชอบของฉัน

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 )

ณ จุดนี้คุณมี someVaryingText เป็นสตริงและกรดเป็นรายการของสตริง คุณสามารถ"".join( acids )สร้างสตริงเดียวได้

ฉันพบว่าสิ่งนี้น่าหงุดหงิดน้อยกว่า (และยืดหยุ่นกว่า) มากกว่า regexes แบบหลายบรรทัด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.