• בלוג
  • ביטויים רגולאריים FTW

ביטויים רגולאריים FTW

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

"יש מתכנתים שחושבים- יש לי בעיה, אשתמש בביטוי רגולארי לפתור אותה. עכשיו יש להם שתי בעיות".

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

1. תיאור המשימה

נתון שם קובץ או שמות קבצים רבים בפורמט ידוע מראש ואנחנו רוצים לשנות את השמות לפורמט אחר. לדוגמא נתון קובץ מוזיקה שמתחיל בשם האומן, ממשיך במספר הרצועה ואחריו שם השיר (ובשביל היופי יש גם מקף באמצע):

filename = "Bob Dylan - 01 You're No Good"

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

in_fmt = '<artist> - <track> <title>'
out_fmt = '[<track>] <title>'

נרצה לכתוב פונקציה שמוציאה משם הקובץ את המאפיינים ב in_fmt ומדביקה אותם למקומות המתאימים ב out_fmt. במקרה כזה ביטויים רגולאריים הם בדיוק הדרך.

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

את הביטוי הרגולארי נרכיב בצורה דינמית מתוך חלקים שהכנו מראש. החלקים הם תוכן התוויות השונות למשל artist יכול להיות כל רצף של תווים אבל track חייב להיות שתי ספרות. המילון הבא מתאר את החלקים:

formats = {
    'artist': r'(?P<artist>.+)',
    'track': r'(?P<track>\d{2})',
    'title': r'(?P<title>.+)',
    'album': r'(?P<album>.+)',
    'year': r'(?P<year>\d{4})',
}

חישוב השם החדש מורכב מ-3 שלבים:

  1. תחילה נייצר ביטוי רגולרי מתוך in_fmt ואבני הבניין ב formats בעזרתו נמשוך את המידע משם הקובץ הנוכחי.

  2. נשתמש בביטוי הרגולרי כדי למשוך את המידע ולקבל מילון עם כל התוויות והערכים המתאימים להן (למשל במילון זה יהיה כתוב שהאומן בשיר הוא Bob Dylan).

  3. נשתמש במילון שיצרנו כדי לשתול את הערכים ב out_fmt במקום ממלאי המקום שנמצאים שם.

קוד הפונקציה כולל הדפסות ביניים נראה כך:

def rename(filename, in_fmt, out_fmt):
    in_re = re.compile(re.sub('<(\w+)>', lambda m: formats[m.group(1)], in_fmt))
    print(in_re)
    data = in_re.search(filename).groupdict()
    print(data)
    return re.sub('<(\w+)>', lambda m: data[m.group(1)], out_fmt)

3. מה יכול להשתבש?

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