ביטוי רגולארי לאיתור ספרות ללא כפילויות

06/07/2017

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

ננסה את התרגיל הבא: בהינתן טקסט הכולל ספרות כפולות ודברים נוספים, יש לשלוף ממנו רשימה של כל הספרות לא כפילויות.

1. פתרון ראשון: מילון

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

str = '2337332439393 hello world'
filter(lambda x: x.isdigit(), { k: True for k in str }.keys())

# result: ['3', '2', '4', '7', '9']

בעיה אחת בקוד היא שהוא לא שומר על הסדר. דרך אחת לתקן היא להחליף את הרכבת המילון ללולאה רגילה, ולעבור ל OrderedDict. דרך שניה היא לכתוב ביטוי רגולארי.

2. פתרון שני: ביטוי רגולארי

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

re.search(r'([0-9]).*\1', str)

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

str = '2337332439393 hello world'
re.findall(r'([0-9]).*\1', str)
# result: ['2', '3']

הספרות 2 ו-3 הופיעו, אבל 9 לא נמצאת למרות שמופיעה מספר פעמים. ההסבר הוא ש findall מוצא את כל המופעים ללא חפיפות, ולכן ההתאמה ל-3 תופסת גם את התשיעיות.

כדי לעקוף את הבעיה ולתפוס את כל הספרות הכפולות נשתמש ב Positive Lookahead. הביטוי הבא מחפש סיפרה שאיפשהו אחריה בביטוי יש מופע נוסף שלה, אבל התאמה לביטוי לא כוללת את כל הרצף עד המופע הבא:

str = '2337332439393 hello world'
re.findall(r'([0-9])(?=.*\1)', str)
# result: ['2', '3', '3', '3', '3', '3', '9', '3']

הפעם כבר התקבלו כל הספרות הכפולות. ומה לגבי מטרת התרגיל המקורית? פשוט החליפו את הסימן ל Negative Lookahead ותקבלו את כל הספרות שאחריהן אין מופע נוסף שלהן:

str = '2337332439393 hello world'
re.findall(r'([0-9])(?!.*\1)', str)
# Result: ['7', '2', '4', '9', '3']