איחוד שורות לבלוק ב Python

09/10/2018

הנה תרגיל קצר שלא ראיתי מיד בהדרכה היום אז רושם כדי לא לשכוח לפעם הבאה - ומקווה שגם אתם תלמדו מזה דבר חדש.

1. האתגר - איחוד שורות

נתון קלט בפורמט הבא:

text = """
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
    options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
    inet 127.0.0.1 netmask 0xff000000
    inet6 ::1 prefixlen 128
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
    nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
en1: flags=963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX> mtu 1500
    options=60<TSO4,TSO6>
    ether 06:00:58:62:a3:00
    media: autoselect <full-duplex>
    status: inactive
p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
    ether 06:0f:24:29:df:4d
    media: autoselect
    status: inactive
"""

כל שורה שאינה מתחילה ברווח מייצגת תחילת בלוק וכל בלוק נמשך עד תחילת הבלוק הבא, כלומר הבלוק הראשון מתחיל ב lo0 ונגמר ב nd6 options.

כתבו קוד פייתון שיהפוך את הטקסט הזה למערך בו כל איבר כולל שרשור של כל השורות שקשורות לבלוק. בדוגמא שלנו במערך יהיו 4 איברים והאיבר הראשון צריך להיות:

'lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>\n\tinet 127.0.0.1 netmask 0xff000000\n\tinet6 ::1 prefixlen 128\n\tinet6 fe80::1%lo0 prefixlen 64 scopeid 0x1\n\tnd6 options=201<PERFORMNUD,DAD>'

2. פיתרון בעזרת ביטוי רגולארי

מנגנון אחד שנותן פיתרון מהיר למצבים כאלה הוא ביטויים רגולאריים. הפונקציה re.split יודעת לקבל ביטוי רגולארי וטקסט ותשבור את הטקסט למערך לפי הביטוי. השאלה היחידה היא מה לכתוב ב split. הבעיה עם re.split שהיא חותכת החוצה את תו ההפרדה ולכן אם ננסה להשתמש במאפיין של "ירידת שורה ואז מילה" נקבל:

>>> import re
>>> re.split('\n\w+:', text)
['', ' flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>\n\tinet 127.0.0.1 netmask 0xff000000\n\tinet6 ::1 prefixlen 128\n\tinet6 fe80::1%lo0 prefixlen 64 scopeid 0x1\n\tnd6 options=201<PERFORMNUD,DAD>', ' flags=8010<POINTOPOINT,MULTICAST> mtu 1280', ' flags=963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX> mtu 1500\n\toptions=60<TSO4,TSO6>\n\tether 06:00:58:62:a3:00\n\tmedia: autoselect <full-duplex>\n\tstatus: inactive', ' flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304\n\tether 06:0f:24:29:df:4d\n\tmedia: autoselect\n\tstatus: inactive\n']

המערך שם אבל שם התקן הרשת, שהיה המילה הראשונה בכל בלוק, נמחק.

אפשרות נוספת היא להשתמש בסוגריים כדי להחזיר את שם התקן הרשת. במצב כזה נקבל:

>>> re.split('\n(\w+:)', text)
['', 'lo0:', ' flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>\n\tinet 127.0.0.1 netmask 0xff000000\n\tinet6 ::1 prefixlen 128\n\tinet6 fe80::1%lo0 prefixlen 64 scopeid 0x1\n\tnd6 options=201<PERFORMNUD,DAD>', 'gif0:', ' flags=8010<POINTOPOINT,MULTICAST> mtu 1280', 'en1:', ' flags=963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX> mtu 1500\n\toptions=60<TSO4,TSO6>\n\tether 06:00:58:62:a3:00\n\tmedia: autoselect <full-duplex>\n\tstatus: inactive', 'p2p0:', ' flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304\n\tether 06:0f:24:29:df:4d\n\tmedia: autoselect\n\tstatus: inactive\n']

עכשיו יש את כל המידע אבל מידע על כל בלוק מחולק לשני איברים עוקבים במערך: הראשון הוא תו ההפרדה והשני הוא שאר הבלוק. אפשר לאחד את הדברים חזרה אם אתם מוכנים להשקיע צעד נוסף בעבודה באמצעות zip:

arr = re.split('\n(\w+:)', text)[1:]
[str(a) + str(b) for a, b in zip(arr[::2], arr[1::2])]

אבל האפשרות הטובה ביותר היא האפשרות השלישית שעושה שימוש נכון ב Look Ahead. זהו פיצ'ר של ביטוי רגולארי שמאפשר לזהות טקסט בתור "שייך לביטוי" אבל לא לתפוס אותו בפועל. בפייתון הפונקציה re.split חייבת שמחרוזת ההפרדה תהיה באורך אחד לפחות. בדוגמא הקודמת מחרוזת ההפרדה היתה באורך משתנה לפי שם התקן הרשת. בואו נוציא את שם ההתקן החוצה ל Look Ahead כך שלא יהיה חלק מהביטוי ואז תו ההפרדה יהיה רק ירידת השורה:

>>> re.split('\n(?=\w+:)', text)[1:]

['lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>\n\tinet 127.0.0.1 netmask 0xff000000\n\tinet6 ::1 prefixlen 128\n\tinet6 fe80::1%lo0 prefixlen 64 scopeid 0x1\n\tnd6 options=201<PERFORMNUD,DAD>', 'gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280', 'en1: flags=963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX> mtu 1500\n\toptions=60<TSO4,TSO6>\n\tether 06:00:58:62:a3:00\n\tmedia: autoselect <full-duplex>\n\tstatus: inactive', 'p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304\n\tether 06:0f:24:29:df:4d\n\tmedia: autoselect\n\tstatus: inactive\n']

ובביטוי רגולארי אחד הצלחנו לאחד את כל השורות לפי בלוקים, כאשר הדבר היחיד שאיבדנו הוא תו ירידת השורה לפני כל בלוק ש re.split חתכה החוצה.