תרגיל בית מספר 1#

‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫תרגיל בית מספר ‪ - 6‬להגשה עד ‪ 6‬ביוני בשעה ‪325::‬‬
‫קיראו בעיון את הנחיות העבודה וההגשה המופיעות באתר הקורס‪ ,‬תחת התיקיה ‪ .assignments‬חריגה‬
‫מההנחיות תגרור ירידת ציון ‪ /‬פסילת התרגיל‪.‬‬
‫הנחיות ספציפיות לתרגיל זה‪:‬‬
‫‪ ‬תשובות לשאלות ‪ 4 ,3 ,1‬א'‪ ,‬ב'‪ ,‬ו‪ 5 -‬יש לממש בקובץ השלד (‪ )skeleton.py‬המצורף לתרגיל זה‪ .‬אין לצרף‬
‫לקובץ ה‪ py-‬את הקוד ששימש לפתרון יתר השאלות‪ .‬לא לשכוח לשנות את שם הקובץ למספר ת"ז שלכם‬
‫לפני ההגשה‪ ,‬אך להשאיר את הסיומת ‪.py‬‬
‫שימו לב שבקובץ השלד יש קוד שמבצע בדיקות של כמה מקרים פשוטים‪ .‬היעזרו בקוד זה כדי לוודא נכונות‬
‫הפלטים שלכם עבור אותם מקרים‪ .‬פתרונות שלא עובדים נכון במקרים פשוטים אלו לא יתקבלו‪ .‬כמובן‪ ,‬על‬
‫הקוד שלכם להיות נכון לכל קלט תקין‪ ,‬ולא רק למקרים שבדוגמאות‪ .‬אסור לשנות את כותרות הפונקציות‬
‫או להוסיף ארגומנטים חדשים‪.‬‬
‫‪‬‬
‫תשובות לכל שאר השאלות יוגשו בקובץ ‪ docx ,doc‬או ‪ pdf‬יחיד‪.‬‬
‫‪‬‬
‫בסה"כ מגישים שני קבצים בלבד‪ .‬עבור סטודנט שמספר ת"ז שלו הוא ‪ 012345678‬הקבצים שיש‬
‫להגיש הם ‪( 012345678.docx‬או ‪ .doc‬או ‪ ).pdf‬ו‪.012345678.py -‬‬
‫‪‬‬
‫הקפידו לענות על כל מה שנשאלתם‪.‬‬
‫‪‬‬
‫במסמך התשובות‪ ,‬תנו תשובות קולעות וברורות‪ ,‬כנדרש בשאלה‪ .‬מטרת הגבלת אורך התשובה היא כפולה‪:‬‬
‫‪ .1‬על מנת שנוכל לבדוק את התרגילים שלכם בזמן סביר‪.‬‬
‫‪ .2‬כדי להרגיל אתכם להבעת טיעונים באופן מתומצת ויעיל‪ ,‬ללא פרטים חסרים מצד אחד אך ללא‬
‫עודף בלתי הכרחי מצד שני‪ .‬זוהי פרקטיקה חשובה במדעי המחשב‪.‬‬
‫עמ' ‪ 1‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫שאלה ‪1‬‬
‫בשאלה זו תממשו מבנה נתונים שנקרא דו‪-‬תור (‪ double-ended queue‬או בקיצור ‪ ,Deque‬קרי‪ֶ :‬דק)‪,‬‬
‫התומך בפעולות הבאות‪:‬‬
‫‪ – head_insert ‬הכנסה בראש הדו‪-‬תור‬
‫‪ – tail_insert ‬הכנסה בזנב הדו‪-‬תור‬
‫‪ – head_remove ‬מחיקה מראש הדו‪-‬תור‬
‫‪ – tail_remove ‬מחיקה מזנב הדו‪-‬תור‬
‫‪ – head ‬החזרת האיבר שבראש הדו‪-‬תור (או ‪ None‬אם הדו‪-‬תור ריק מאיברים)‬
‫‪ - tail ‬החזרת האיבר שבזנב הדו‪-‬תור (או ‪ None‬אם הדו‪-‬תור ריק מאיברים)‬
‫על כל הפעולות להתבצע בסיבוכיות זמן )‪ O(1‬במקרה הגרוע‪ .‬אין להשתמש במימושים קיימים של ‪,Deque‬‬
‫למשל ‪.itertools.deque‬‬
‫ממשו מחלקה בשם ‪ ,Deque‬הכוללת את המתודות הנ"ל (וכמובן __‪ ,__init‬ו‪ ,__repr__ -‬ראו בהמשך)‪.‬‬
‫איברי הדו‪-‬תור צריכים להיות עצמים מהמחלקה ‪ Node‬שראיתם בכיתה‪ .‬המחלקה ‪ Node‬וכותרות‬
‫המתודות של המחלקה ‪ Deque‬מופיעות בקובץ השלד שמסופק עם התרגיל‪.‬‬
‫ממשו את המתודה __‪ __repr‬כך שתחזיר מחרוזת המכילה את איברי הדו‪-‬תור עם רווחים ביניהם (אך לא‬
‫בהתחלה או בסוף)‪ ,‬כאשר ראש התור יהיה האיבר השמאלי‪ .‬מטרת הנחיה זו היא ליצור אחידות שתאפשר‬
‫בדיקת הקוד שלכם ע"י הרצות‪.‬‬
‫דוגמאות הרצה‪:‬‬
‫)(‪>>> dq = Deque‬‬
‫)‪>>> dq.head_insert(1‬‬
‫)‪>>> dq.head_insert(2‬‬
‫)‪>>> dq.head_insert(3‬‬
‫‪>>> dq‬‬
‫‪321‬‬
‫)‪>>> dq.tail_insert(4‬‬
‫‪>>> dq‬‬
‫‪3214‬‬
‫)(‪>>> dq.head_remove‬‬
‫‪>>> dq‬‬
‫‪214‬‬
‫)(‪>>> dq.tail_remove‬‬
‫‪>>> dq‬‬
‫‪21‬‬
‫)(‪>>> dq.head‬‬
‫‪2‬‬
‫)(‪>>> dq.tail‬‬
‫‪1‬‬
‫עמ' ‪ 2‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫שאלה ‪3‬‬
‫כזכור‪ ,‬פקטור העומס של טבלת ‪ hash‬מוגדר כ‪ ,α = n/m -‬כאשר ‪ m‬הוא גודל הטבלה ו‪ n -‬הוא מספר‬
‫האיברים בה‪ .‬בד"כ ייקבע גודל הטבלה כתלות ב‪ ,n -‬מתוך מטרה להגביל את ‪ .α‬למשל‪ ,‬אם ‪ m=½n‬אז ‪.α=2‬‬
‫בשאלה זו נטפל במצב שבו איננו יודעים מראש כמה איברים צפויים להיכנס לטבלת ה‪ hash -‬שלנו‪ .‬פתרון‬
‫אפשרי הוא להקצות טבלה בגודל כלשהו‪ ,‬ובכל פעם ש‪ α -‬גדל מעבר לערך מסוים‪ ,‬להגדיל את הטבלה‪.‬‬
‫פעולה כזו מכונה ‪ ,rehash‬וכרוכה במעבר על כל הטבלה והכנסת כל האיברים שלה לטבלה החדשה‪ .‬כמובן‪,‬‬
‫הדבר מצריך גם שינוי בפונקצית ה‪ ,hash -‬כדי שתמפה ערכים לטווח האינדקסים של הטבלה החדשה‪.‬‬
‫נניח שהתנגשויות מטופלות בשיטת השרשור (‪ ,)chaining‬שהגודל ההתחלתי של הטבלה הוא ‪ ,1‬ושמגדילים‬
‫את הטבלה בכל פעם ש‪ α -‬מגיע ל‪ .1 -‬בכל אחד מהסעיפים הבאים מופיעה אסטרטגיה אפשרית לקביעת‬
‫גודל הטבלה החדשה‪:‬‬
‫א‪ .‬בעת הגדלת הטבלה‪ ,‬הגודל החדש יהיה ‪.m+1‬‬
‫ב‪ .‬בעת הגדלת הטבלה‪ ,‬הגודל החדש יהיה ‪.m+7‬‬
‫ג‪ .‬בעת הגדלת הטבלה‪ ,‬הגודל החדש יהיה ‪.2m‬‬
‫בכל סעיף‪ ,‬נתחו את סיבוכיות הזמן הכוללת של סדרת ‪ N‬הכנסות רצופות (כלומר ‪ N‬הכנסות‪ ,‬בזו אחר זו‪,‬‬
‫ללא מחיקות)‪ .‬תנו תשובה אסימפטוטית במונחי )…(‪ O‬כתלות ב‪ .N -‬יש לתת נימוק קצר ‪ -‬שורה או שתיים‪.‬‬
‫שאלה ‪2‬‬
‫א‪ .‬ממשו גנרטור בעל החתימה )‪ take_only(it, predicate, n‬שמקבל איטרטור ‪ ,it‬ופונקציה ‪predicate‬‬
‫יקט)‪ .‬הגנרטור מייצר את‬
‫המקבלת ארגומנט יחיד ומחזירה ‪( True/False‬פונקציה כזאת נקראת פְּ ֶר ִד ָ‬
‫האיברים של ‪ it‬לפי סדרם אם הם עונים על התנאי ‪ .predicate‬כלומר ‪ take_only‬מייצר תת‪-‬סדרה של‬
‫איברי ‪ it‬המכילה רק איברים ‪ x‬עבורם ‪ .predicate(x)==True‬אם ‪ n‬איברים רצופים אינם עונים‬
‫לתנאי‪ take_only ,‬מפסיק לייצר איברים וזורקת ‪ exception‬ע"י ‪ .raise StopIteration‬לדוגמא‪:‬‬
‫))‪>>> list(take_only(iter(range(30)), lambda x: x%3==1 , 5‬‬
‫]‪[1, 4, 7, 10, 13, 16, 19, 22, 25, 28‬‬
‫))‪>>> list(take_only(iter(range(30)),lambda x: x<10 or x%7==0 ,5‬‬
‫]‪[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 14‬‬
‫ב‪ .‬ממשו גנרטור בעל החתימה )‪ blocks(it, k‬שמקבל איטרטור ‪ it‬ומספר ‪ k‬ומחזיר רשימות של איברים‬
‫עוקבים מתוך ‪ it‬שאורכן ‪ .k‬למשל‪ ,‬שלושת האיברים הראשונים שיוחזרו ע"י ‪ blocks‬עם ‪ k=3‬הם‪:‬‬
‫כאשר‬
‫הם איברי ‪ .it‬אם ‪ it‬מייצר מס' איברים‬
‫סופי שאינו מתחלק ב‪ blocks ,k-‬יחזיר בלוק אחרון שאורכו קצר יותר מ‪ .k-‬לדוגמא‪:‬‬
‫))‪>>> list(blocks(iter(range(10)),5‬‬
‫]]‪[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9‬‬
‫))‪>>> list(blocks(iter(range(10)),3‬‬
‫]]‪[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9‬‬
‫שימו לב‪ :‬בשני הסעיפים האיטרטורים שניתנים כקלט אינם בהכרח סופיים‪.‬‬
‫עמ' ‪ 3‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫שאלה ‪4‬‬
‫מטרת השאלה היא לתרגל את העיקרון של אלגוריתם קארפ‪-‬רבין‪ ,‬וכמו כן לצבור מיומנות בהבנת קוד חלקי‬
‫שניתן לכם לפתרון בעיה מסויימת‪ ,‬ולהשלים אותו‪.‬‬
‫בשאלה זו נשוב לבעיה של חיפוש תת‪-‬מחרוזת משותפת באורך מקסימלי‪ ,‬בה נתקלנו בתרגיל בית מס' ‪ ,5‬אך‬
‫הפעם ננקוט גישה שונה‪ .‬כשאנחנו מחפשים מחרוזת משותפת מקסימלית‪ ,‬אנחנו מחפשים מחרוזות‬
‫משותפות באורך הולך ועולה‪ ,‬נסמן את האורך ב‪ . -‬בשאלה זו נחזיק את תתי‪-‬המחרוזות בטבלת ‪hash‬‬
‫ונשתמש ב‪ fingerprint-‬של אלגוריתם של ‪ Karp-Rabin‬בתור פונקצית ה‪ .hash-‬ננצל את התכונות של‬
‫אלגוריתם ‪ Karp-Rabin‬ונגדיל באופן הדרגתי וביעילות את אורכי ה"חלונות" עבורם ה‪fingerprints-‬‬
‫מחושבים‪ .‬להלן הסבר מפורט‪.‬‬
‫נשים לב לתכונה הבאה של טביעות האצבע של אלגוריתם ‪:Karp-Rabin‬‬
‫עבור המחרוזת‬
‫‪ ,‬נסמן ב‪-‬‬
‫את טביעת האצבע של החלון‬
‫ניתן לחשב את‬
‫מתוך‬
‫באמצעות כלל הורנר )‪ (Horner’s rule‬שהוזכר בהרצאות‪ .‬לכן‪ ,‬בהינתן‬
‫‪ ,‬נסמנה‬
‫רשימת כל טביעות האצבע של חלונות באורך‬
‫‪.‬‬
‫‪ ,‬ונוכל בקלות לחשב ממנה את‬
‫(כל טביעות האצבע באורך )‪ .‬נמשיך להשתמש בסימונים שהגדרנו כאן לאורך השאלה‪.‬‬
‫בקובץ השלד ישנה פונקציה ‪ find_longest‬שמבצעת חיפוש תת‪-‬מחרוזת משותפת מקסימלית של שתי‬
‫מחרוזות‪ .‬המימוש של ‪ find_longest‬מופיע כבר בקובץ‪ ,‬והיא קוראת לפונקציות אחרות‪ has_match :‬שגם‬
‫המימוש שלה כבר מופיע‪ ,‬ושתי הפונקציות ‪ – extend_fingerprints, make_hashtable‬עבורן המימוש חסר‪,‬‬
‫ואתם מתבקשים להשלים אותו‪ ,‬כמוסבר בהמשך‪ .‬כאמור‪ ,‬חלק ממטרת התרגיל היא לנסות ולהבין את‬
‫הקוד שמומש כבר‪.‬‬
‫‪ .‬הפרמטרים‬
‫לאורך התרגיל‪ ,‬כשנחשב טביעות אצבע‪ ,‬נשתמש בפרמטרים‬
‫שונים מאלה שראינו בשיעור‪ ,‬זאת על מנת שניתן יהיה להשתמש בטביעות האצבע כפונקציית ‪hash‬‬
‫ביעילות‪ .‬לו היינו משתמשים ב‪-‬‬
‫תאים‪.‬‬
‫היינו נאלצים להשתמש בטבלת ענק עם‬
‫סעיף א‬
‫ממשו את הפונקציה )‪ .extend_fingerprints(text, fingers, l, basis, r‬הפונקציה מקבלת מחרוזת טקסט‬
‫)‪ (text‬ואת רשימת טביעות האצבע‬
‫הפונקציה מחשבת את‬
‫של הטקסט עבור חלונות באורך‬
‫‪.‬‬
‫– טביעות האצבע של הטקסט עבור חלונות באורך (זהו הפרמטר‬
‫השלישי שמועבר לפונקציה)‪ .‬על הפונקציה לחשב את‬
‫ע"י הארכה של טביעות האצבע הנתונות ב‪-‬‬
‫‪ ,‬בזמן )‪ O(1‬עבור כל טביעת אצבע‪ .‬הפונקציה אינה מחזירה ערך‪ ,‬אלא מעדכנת את ‪fingers‬‬
‫כך שהרשימה תכיל את‬
‫במקום את‬
‫‪ .‬טביעות האצבע מחושבות בבסיס ‪,basis‬‬
‫מודולו ‪ ,r‬כפי שאנחנו מצפים מאלגוריתם ‪.Karp-Rabin‬‬
‫עמ' ‪ 4‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫שימו לב‪ :‬כאשר גדל ב‪ ,1 -‬אורך הרשימה‬
‫משתנה – הוא קטן ב‪( 1 -‬מדוע?)‪.‬‬
‫דוגמת הרצה‪:‬‬
‫]‪>>> fingers = [0, 0, 0, 0, 0, 0‬‬
‫)‪>>> extend_fingerprints("hello", fingers, 1‬‬
‫‪>>> fingers‬‬
‫]‪[104, 101, 108, 108, 111‬‬
‫)‪>>> extend_fingerprints("hello", fingers, 2‬‬
‫‪>>> fingers‬‬
‫]‪[26725, 25964, 27756, 27759‬‬
‫)‪>>> extend_fingerprints("hello", fingers, 3‬‬
‫‪>>> fingers‬‬
‫]‪[26016, 93342, 27813‬‬
‫סעיף ב‬
‫של טקסט‬
‫ממשו את הפונקציה )‪ .make_hashtable(fingers, table_size‬הפונקציה מקבלת את‬
‫כלשהו (הטקסט ואורך החלון לא ידועים ואינם חשובים כאן) ומחזירה טבלת ‪ hash‬בגודל ‪,table_size‬‬
‫כאשר תא ‪ j‬בטבלה מכיל את רשימת האינדקסים ‪ i‬שעבורם מתקיים ‪ ,fingers[i] == j‬מסודרים בסדר‬
‫עולה‪ .‬לדוגמא‪:‬‬
‫)‪>>> make_hashtable([0, 0, 1, 2, 1, 2, 1, 2, 3, 3, 3, 0, 0], 4‬‬
‫]]‪[[0, 1, 11, 12], [2, 4, 6], [3, 5, 7], [8, 9, 10‬‬
‫אנחנו מניחים כאן שהטבלה גדולה מספיק כדי להכיל את טביעת האצבע המקסימלית‪.‬‬
‫סעיף ג‬
‫מה הסיבוכיות של ‪ has_match‬כתלות בפרמטרים ‪ n2 ,n1‬ו‪ ,l -‬שהם בהתאמה האורך של ‪ ,text1‬האורך של‬
‫‪ ,text2‬וגודל ה"חלון" הנוכחי? תנו תשובה במונחים של )…(‪ ,O‬והסבירו במשפט או שניים‪.‬‬
‫סעיף ד‬
‫לתרגיל מצורפים שני קבצי טקסט ‪ wonderland.txt, looking_glass.txt‬המכילים את תוכן הספרים‬
‫”‪“Alice’s Adventures in Wonderland‬‬
‫”‪“Through the Looking Glass, and What Alice Found There‬‬
‫מאת לואיס קרול‪ .‬השתמשו בפונקציה ‪( find_longest‬המימוש של פונקציה זו נתון כבר כאמור)‪ ,‬כדי למצוא‬
‫את אורכה של תת‪-‬מחרוזת משותפת מקסימלית לשני הספרים הללו‪ .‬כדי להימנע מהבדלים בין הטקסטים‬
‫שנובעים מסגנון ועריכה נסיר מהטקסט חלק מסימני הפיסוק ורווחים ונהפוך אותיות גדולות לקטנות‪.‬‬
‫השתמשו בפונקציה ‪ format_text‬המצורפת לקובץ השלד כדי לנקות את הטקסטים‪ ,‬וכתבו את התשובה‬
‫הסופית במסמך הטקסט שאתם מגישים‪ .‬אין לכלול בהגשה את הקוד ששימש אתכם בסעיף זה‪.‬‬
‫עמ' ‪ 5‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫שאלה ‪:‬‬
‫צופן קיסר הינו שיטת הצפנה עתיקה בה השתמש יוליוס קיסר כדי להעביר בבטחה פקודות למפקדי צבאו‪.‬‬
‫‪ .‬האלגוריתם מחליף כל אות בטקסט באות‬
‫אלגוריתם ההצפנה מקבל טקסט ‪ txt‬ומפתח‬
‫שנמצאת ‪ k‬מקומות ימינה ממנה באלפבית‪ .‬ההזזה היא מעגלית‪ ,‬כלומר ‪ k‬האותיות האחרונות באלפבית‬
‫מוחלפות ב‪ k-‬האותיות הראשונות‪ .‬בשרטוט מומחשת ההחלפה שמתבצעת עבור ‪:k=3‬‬
‫…‬
‫‪f‬‬
‫‪e‬‬
‫‪d‬‬
‫‪c‬‬
‫‪b‬‬
‫‪a‬‬
‫‪z‬‬
‫‪y‬‬
‫‪x‬‬
‫…‬
‫…‬
‫‪i‬‬
‫‪h‬‬
‫‪g‬‬
‫‪f‬‬
‫‪e‬‬
‫‪d‬‬
‫‪c‬‬
‫‪b‬‬
‫‪a‬‬
‫…‬
‫לשם פשטות‪ ,‬נניח כי ההצפנה משנה אותיות אנגליות קטנות בלבד (‪ ,)lowercase‬וכי כל סימן שאינו כזה‬
‫נשאר ללא שינוי‪ .‬למשל‪ ,‬עבור הטקסט ”!‪ txt = ”attack at once‬והמפתח ‪ k=3‬נקבל‪.”dwwdfn dw rqfh!” :‬‬
‫טקסט מוצפן קרוי ‪ .cipher‬קל יחסית לשבור צופן קיסר (כלומר לגלות את המפתח) ולשחזר את ההודעה‬
‫המקורית מתוך ה‪ – cipher -‬שהרי ישנם בסך הכל ‪ 26‬מפתחות אפשריים‪.‬‬
‫בשאלה זו נכתוב קוד לשבירת צופן קיסר‪ .‬כאשר אדם עובר על ‪ 26‬המפתחות האפשריים‪ ,‬קל לו לזהות את‬
‫המפתח הנכון משום שהטקסט שמתגלה מורכב ממילים תקינות באנגלית‪ .‬עבור מחשב קשה יותר לזהות‬
‫שהמילים בטקסט הינן מילים תקינות‪ .‬לכן ננצל תכונה אחרת של הטקסט‪ :‬כאשר נבחר במפתח הנכון‪,‬‬
‫שכיחויות האותיות בטקסט המפוענח צריכה להיות דומה לשכיחות האותיות בשפה האנגלית‪.‬‬
‫סעיף א‬
‫כתבו פונקציה )‪ caesar(txt, k‬שמחזירה את הטקסט ‪ txt‬לאחר הצפנת קיסר עם מפתח ‪ .k‬נשים לב שע"י‬
‫בחירה נכונה של ‪ k‬ניתן להשתמש בפונק' ‪ caesar‬גם בשביל הפענוח‪ .‬לדוגמא‪:‬‬
‫)‪>>> caesar(“attack at once!”, 3‬‬
‫'!‪'dwwdfn dw rqfh‬‬
‫)‪>>> caesar(caesar(“attack at once!”,3), 23‬‬
‫'!‪'attack at once‬‬
‫סעיף ב‬
‫כתבו פונקציה )‪ frequency(txt‬המקבלת טקסט ‪ txt‬ומחזירה מילון שכיחויות – מילון שבו המפתחות‬
‫(‪ )keys‬הם אותיות האלפבית האנגלי והערכים (‪ )values‬הם השכיחות (מספר ההופעות היחסי) של האותיות‬
‫בטקסט‪ .‬השכיחות של אות שאינה מופיעה בטקסט מוגדרת כ‪ .0-‬למשל‪ ,‬עבור הטקסט "!‪:"attack at once‬‬
‫)"!‪frequency("attack at once‬‬
‫‪>>> {‘a’: 0.25, ‘c’: 0.166, ‘b’: 0.0, ‘e’: 0.0833, ‘d’: 0.0, ‘g’: 0.0, ‘f’: 0.0, ‘i’: 0.0, ‘h’: 0.0, ‘k’:‬‬
‫‪0.0833, ‘j’: 0.0, ‘m’: 0.0, ‘l’: 0.0, ‘o’: 0.0833, ‘n’: 0.0833, ‘q’: 0.0, ‘p’: 0.0, ‘s’: 0.0, ‘r’: 0.0,‬‬
‫}‪‘u’: 0.0, ‘t’: 0.25, ‘w’: 0.0, ‘v’: 0.0, ‘y’: 0.0, ‘x’: 0.0, ‘z’: 0.0‬‬
‫עמ' ‪ 6‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬
‫אוניברסיטת תל אביב ‪ -‬בית הספר למדעי המחשב‬
‫מבוא מורחב למדעי המחשב‪ ,‬אביב ‪3102‬‬
‫סעיף ג‬
‫בהינתן שני מילוני שכיחויות ‪( freq1, freq2‬כפי שהוגדרו בסעיף ב') המייצגים את השכיחויות בשני‬
‫טקסטים ‪ ,t1, t2‬נגדיר את המרחק בין המילונים באופן הבא‪:‬‬
‫המרחק שהגדרנו אולי נראה משונה‪ ,‬אך למעשה מדובר בבחירה די טבעית‪ .‬נבחן תכונות של המרחק‪:‬‬
‫‪ ‬המדד נותן משקל גדול לאותיות שהשכיחות שלהן שונה מאוד בין שני הטקסטים‪ ,‬כך שעבור‬
‫טקסטים שונים מאוד יתקבל מרחק גדול‪.‬‬
‫‪ distance(freq1, freq1)=0 ‬כלומר המרחק של טקסט מעצמו הינו ‪.0‬‬
‫ממשו את הפונקציה )‪ distance(freq1,freq2‬שתחזיר את המרחק בין שני מילונים נתונים‪.‬‬
‫סעיף ד‬
‫בסעיף זה נכתוב פונקציה שתשבור את צופן קיסר באופן אוטומטי‪ .‬הפונקציה תעשה שימוש ב‪– corpus-‬‬
‫טקסט ארוך יחסית באנגלית ממנו היא תלמד את שכיחויות האותיות בשפה האנגלית‪ .‬הפונקציה מקבלת‬
‫טקסט מוצפן (ע"י צופן קיסר) ומנסה לפענח אותו באמצעות כל אחד מ‪ 26 -‬המפתחות האפשריים‪.‬‬
‫הפונקציה מזהה את המפתח הנכון לפי מילון השכיחויות שלו‪ :‬כאשר הטקסט מפוענח כהלכה‪ ,‬שכיחויות‬
‫האותיות בטקסט דומות לשכיחויות ב‪( corpus -‬כלומר המרחק בין השכיחויות בטקסט המפוענח‬
‫לשכיחויות ב‪ corpus-‬הינו מינימלי)‪.‬‬
‫כתבו פונקציה )‪ break_caesar(corpus, cipher‬ששוברת את צופן קיסר באופן שתואר למעלה‪ .‬הפונקציה‬
‫מקבלת שני טקסטים ‪ corpus‬ו‪ cipher -‬כמחרוזות‪ ,‬מפענחת את ה‪ cipher -‬ומחזירה ‪ tuple‬של שני איברים‪:‬‬
‫האיבר הראשון הוא ‪ – k‬המפתח עבורו התקבל מרחק מינימלי‪ ,‬והאיבר השני הוא הטקסט המפוענח‪.‬‬
‫להלן דוגמת הרצה‪ ,‬שבה הקורפוס הוא הטקסט של ‪ Alice's Adventures in Wonderland‬מאת ‪Lewis‬‬
‫‪ Carroll‬מתוך פרוייקט גוטנברג ‪:‬‬
‫‪>>> import urllib.request‬‬
‫‪>>> with urllib.request.urlopen("http://www.gutenberg.org/cache/epub/11/pg11.txt") as r:‬‬
‫)'‪corpus = r.read().decode('utf-8‬‬
‫)"!‪>>> k, text = break_caesar(corpus.lower(),"dwwdfn dw rqfh‬‬
‫‪>>> k, text‬‬
‫)'!‪(23, 'attack at once‬‬
‫כדי לבדוק את עצמכם הריצו את הפונקציה ופענחו את הקבצים ‪ cipher1.txt, cipher2.txt‬המצורפים‬
‫לתרגיל (השתמשו בקורפוס הנ"ל)‪.‬‬
‫שימו לב‪ :‬לנוחיותכם‪ ,‬קובץ השלד מכיל‪ ,‬מלבד כותרות הפונקציות שעליכם לממש‪ ,‬גם משתנה גלובלי‬
‫‪ – alphabet‬מחרוזת של כל האותיות באלפבית האנגלי‪ .‬השימוש בפונקציות עזר מותר‪ ,‬כרגיל‪.‬‬
‫בונוס ‪ :‬נק'‪ :‬מהיכן לקוח כל אחד מהטקסטים ‪ cipher1.txt‬ו‪?cipher2.txt -‬‬
‫סוף‪.‬‬
‫עמ' ‪ 7‬מתוך ‪7‬‬
‫‪CC BY-NC-SA 3.0‬‬