מבחן בקורס מבוא מורחב למדעי המחשב CS1001.py ביה"ס למדעי המחשב, אונ' תל אביב סמסטר א' 2017-18, מועד ב, 23/3/2018 מרצים: דניאל דויטש, אמיר רובינשטיין מתרגלים: מיכל קליינבורט, אמיר גלעד משך הבחינה: 3 שעות. חומר עזר מותר: 2 דפי עזר )דו צדדיים( בגודל A4 כל אחד. במבחן 12 עמודים מודפסים בידקו שכולם בידיכם. בנוסף, בסוף הבחינה ישנו דף נוסף, ריק, לשימוש ב"מקרה חירום" בלבד. יש לכתוב את כל התשובות בטופס הבחינה. המחברת תשמש כטיוטה בלבד ולא תיבדק. יש לענות על כל השאלות. בכל השאלות, אלא אם נכתב במפורש אחרת: אם עליכם לכתוב פונקציה, אין צורך לבדוק את תקינות הקלט שלה. - מותר להסתמך על סעיפים קודמים, גם אם לא עניתם עליהם. - ניתן לצטט טענות שנטענו בהרצאה או בשיעורי התרגול. במקרה זה יש לכתוב "בהרצאה/תרגול ראינו כי...". - אנו ממליצים לא "להיתקע" על אף שאלה, אלא להמשיך לשאלות אחרות ולחזור לשאלה אח"כ. בכל סעיף ניתן לכתוב "איני יודע/ת" ולא לכתוב שום טקסט נוסף. במקרה זה יינתן 20% מציון הסעיף )מעוגל כלפי מטה(. ישלכתוב את כל התשובות במקום המוקצב ובכתב קריא. תשובות ובהן חריגות משמעותיות מהמקום המוקצב, או תשובות הכתובות בכתב קטן מדי, לא ייקראו ולא יקבלו ניקוד, או שיקבלו ניקוד חלקי בלבד.תשובות שדורשות מאמצים רבים להבנתן גם כן עלולות לגרור הורדת ציון. טבלת ציונים:)לשימוש הבודקים( שאלה ערך ניקוד בהצלחה! 20 20 15 20 25 100 1 2 3 4 5 סה"כ
שאלה )20 1 נק'( בשאלה זו נעסוק בבעיה הבאה: בהינתן רשימת מספרים שלמים גדולים ממש מ- 0 טבעיים L ומספר שלם k גדול ממש מ-, 0 האם קיימת תת קבוצה )לא בהכרח מוכלת ממש( של איברי L שסכום המספרים בה הוא k בדיוק? א. השלימו את הקוד הרקורסיבי הבא שפותר את הבעיה, כלומר הפונקציה שתשלימו תקבל כקלט רשימה L ומספר k, ותחזיר True אם קיימת תת קבוצה כאמור, אחרת תחזיר.False למשל עבור הרשימה [1,4,8,9]=L וk=18, התשובה תהיה true כי תת הקבוצה {8,9,1} מקיימת שסכום איבריה הוא 18. הנחיה: אין להשתמש ב- slicing בסעיף זה. def subsetsum(l, k): return s_rec(l,0,k): def s_rec(l, i, k): if : if : return True else: return False else: return ב. מהי סיבוכיות זמן הריצה של הקוד שכתבתם במקרה הגרוע, במונחי n )אורך הרשימה L(? הניחו כי פעולות אריתמטיות רצות בזמן קבוע. הסבר: O( ) 2
ג. ממשו כעת גרסא של פונקצית המעטפת והפונקציה הרקורסיבית כך שישתמשו בממואיזציה. 3
ד. )i( תנו דוגמא לקלט L,k עבורו אתם צופים כי גרסת הקוד עם ממאויזציה )סעיף ג( תהיה עדיפה מגרסת הקוד ללא ממואיזציה )סעיף א(. הסבירו תשובתכם. )ii( תנו דוגמא לקלט L,k עבורו אתם צופים כי גרסת הקוד עם ממאויזציה )סעיף ג( לא תהיה עדיפה מגרסת הקוד ללא ממואיזציה )סעיף א(. הסבירו תשובתכם. 4
שאלה )20 2 נק'( בשאלה זו נעסוק בבעיית העלאה בחזקה מודולרית. א. עליכם לממש אלגוריתם יעיל לבעיה זו, על ידי השלמת הקוד הבא. אין חובה להשתמש בכל השורות. הנחיה מחייבת: אין להשתמש בחישוב מודולו שתיים )%2) בפתרון שלכם. def modpower(a,b,c): """ computes a**b mod c using iterated squaring assumes b is nonnegative integer """ result = 1 # a**0 while b>0: if b%3 == 0: if b%3 == 1: if b%3 == 2: return result ב. נסמן ב- n a, n b, n c את מספר הביטים בהתאמה של.a, b, c מהו מספר האיטרציות הדרוש במקרה הגרוע לקוד שכתבתם, כפונקציה של n, a, n b, n c כולם או חלקם? O( ) הסבירו בקצרה: 5
שאלה )15 3 נק'( from random import * הנה תזכורת לאלגוריתם ניוטון-רפסון שראינו בכיתה, למציאת שורש של פונקציה ממשית. def diff_param(f, h=0.001): return (lambda x: (f(x+h)-f(x))/h) def NR(func, deriv=none, epsilon=10**(-8), n=100, x0=none): if deriv is None: deriv = diff_param(func) if x0 is None: x0 = uniform(-100.0,100.0) x = x0; y = func(x) for i in range(n): if abs(y) < epsilon: print ("x=", x, "f(x)=", y, "convergence in", i, "iterations") return x elif abs(deriv(x)) < 10**(-25): #arbitrary small value print ("zero derivative, x0=", x0, " i=", i, " xi=", x) return None else: print("x=", x, "f(x)=", y) x = x - func(x)/deriv(x) y = func(x) print("no convergence, x0=",x0," i=",i, " xi=", x) return None א. נתונה פונקציה ליניארית lambda x:a*x+b עבור a,b קבועים כלשהם, 0 a. מה מהבאים יקרה עבור הרצת הפקודה (b? NR(lambda x : a*x + הקיפו בעיגול את התשובה הנכונה והסבירו. תתבצע איטרציה בודדת ובאיטרציה הבאה אחריהיה יוחזר ערך בשורה return x יתבצע מספר לא ידוע של איטרציות )בין 1 ל- n(, ולאחריהן יוחזר ערך בשורה return x תתבצע איטרציה בודדת וובאיטרציה הבאה אחריה יודפס derivative..." "zero יתבצע מספר לא ידוע של איטרציות )בין 1 ל- n(, ולאחריהן יודפס derivative..." "zero תתבצע איטרציה בודדת ובאיטרציה הבאה אחריה יודפס convergence " "no יתבצע מספר לא ידוע של איטרציות )בין 1 ל- n(, ולאחריהן יודפס convergence " "no )1 )2 )3 )4 )5 )6 הסבר: 6
ש)ו ב. מטה הסייבר של צפון קוריאה החליט לחבל בפונקציה,NR ולשנות אותה כך שערך הנגזרת בכל שלב יהיה מספר אקראי כלשהו בין 0 ל- 1. לשם כך, משתמשים בפונקציה random מהספריה,random שלא מקבלת שום פרמטר ומחזירה מספר בין 0 ל- 1 בכל פעם שקוראים לה. deriv = diff_param(func) השינוי בפונקציה מתבטא בהחלפת השורה ורה זו בלבד, שאר הקוד ללא שום שינוי(. שבפונקציה NR מה מהבאים הוא הצורה הנכונה לכתוב זאת, כך ש- NR עדיין תרוץ )כלומר ריצתה תסתיים, בין אם בהחזרת שורש או?)None הקיפו בעיגול. deriv = random )1 deriv = random() )2 deriv = lambda x: random )3 deriv = lambda x: random() )4 ג. נתונות הפונקציות הבאות: >>> f1 = lambda x: x**2-1 >>> f2 = lambda x: f1(x) if abs(x)!=1 else 999 >>> diff_f2 = lambda x: 2*x מריצים: >>> NR(f2, diff_f2) מה מהבאים התרחיש הסביר ביותר, כלומר מה יתרחש עבור רוב הבחירות של ניחוש התחלתי? הסבירו. והריצה תסתיים יודפס derivative..." "zero 1( והריצה תסתיים יודפס convergence " "no 2( הריצה תסתיים עם החזרת שורש שקרוב ל- 1 או 1- עד כדי.epsilon=10**(-8) 3( הריצה תסתיים עם החזרת שורש שאינו בהכרח קרוב ל- 1 או 1- עד כדי.epsilon=10**(-8) 4( הסבר: 7
שאלה )20 4 נק'( להלן קוד של פונקציה אשר מקבלת מספר טבעי כלשהו n כקלט. 1. def what(n): 2. a = [True] * n 3. a[0] = a[1] = False 4. for (i, val) in enumerate(a): 5. if val == True: 6. for j in range(2*i, n, i): 7. a[j] = False 8. print("i=", i, a) 9. return a תזכורת: לולאת for עם enumerate עוברת על כל הזוגות element) (index, כאשר index הוא האינדקס של האיבר element באוסף כלשהו. למשל: >>> lst = ["a", "b", "c"] >>> for (i, e) in enumerate(lst): print(i,e) 0 a 1 b 2 c א. מה יודפס בשורה 8 של הפונקציה עבור הפקודה what(8) כאשר 1=i? i = 1 [,,,,,,, ] ב. מה יהיה הערך המוחזר של?what(8) [,,,,,,, ] ג. נסמן את הרשימה המוחזרת מ- what(100) ב-.lst מה יהיה הערך בתא?lst[89] סמנו את התשובה והסבירו בקצרה. True.a False.b הסבר: ד. מהי הסיבוכיות של הפונקציה what כתלות ב- n? סמנו את הביטוי ההדוק ביותר מבין החסמים הבאים )סמנו תשובה אחת בלבד( O(log(n)).a O(n log(n)).b O(n 2 ).c O(n log 2 n).d 8
ה. ראו את הגרסה הבאה של,what ששמה.what2 ההבדל היחיד הוא בשורה 6, במקום i*2 מופיע :i*i 1. def what2(n): 2. a = [True] * n 3. a[0] = a[1] = False 4. for (i, val) in enumerate(a): 5. if val == True: 6. for j in range(i*i, n, i): 7. a[j] = False 8. print("i=", i, a) 9. return a נסמן את הרשימה המוחזרת מ- what2(100) ב-.lst מה יהיה הערך בתא?lst[89] סמנו את התשובה והסבירו בקצרה. True.a False.b הסבר: 9
שאלה )25 5 נק'( בקורס דנו במבנה הנתונים רשימה מקושרת list( )linked אשר מורכב מצמתים )nodes( שמשורשרים אחד לשני. באופן יותר מדויק, האובייקט Linked_list מכיל מצביע לצומת הראשון שלו מטיפוס Node וכל אובייקט מטיפוס.)None נוסף, שהוא הצומת הבא ברשימה )או Node ומצביע לאובייקט )value( מכיל ערך כלשהו Node בסוף השאלה מופיע הקוד של המחלקות Linked_list ו- Node שראיתן בכיתה. הקוד מכיל רק את מתודות האיתחול ואת הפונקציות add_at_start ו- reverse שראינו בתרגול. א. השלימו את הקוד של המתודה reverse_rec(self) ששייכת למחלקה. Linked_list אין חובה להשתמש בכל השורות. מתודה זו הופכת את הכיוון של רשימה מקושרת, בדיוק כמו המתודה reverse מהתרגול )תמונת הזיכרון לאחר הפעלת reverse_rec תהיה זהה לזו שהיתה מתקבלת לאחר הפעלת.)reverse על המתודה לפעול בסיבוכיות זמן O(n) כאשר n הוא מספר הצמתים ברשימה. def reverse_rec(self): def reverse_node(curr, prev): if : return tmp = curr.next return self.next = reverse_node(, ) 10
ב. נניח שהשדה value של כל צומת ברשימה המקושרת מכיל תו יחיד )למשל a (. התבוננו במתודה הבאה: def what(self): a = '' b = '' lp = self rp = self loc = self.len for i in range(self.len//2+1): if a!= b: return False lp = lp.next a = lp.value rp = self for j in range(loc): rp = rp.next b = rp.value loc -= 1 return True :True תחזיר what באורך 2 לכל הפחות, כך שהמתודה s כתבו מחרוזת I. >>> s = >>> l = Linked_list(s) >>> l.what() True הסבר: :False תחזיר what באורך 2 לכל הפחות, כך שהמתודה s כתבו מחרוזת.II >>> s = >>> l = Linked_list(s) >>> l.what() False הסבר: 11
ג. מהי הסיבוכיות של הפונקציה what כתלות באורך הרשימה,self אותו נסמן ב- n? תנו תשובה הדוקה ככל שתוכלו במונחי ()O, והסבירו. תשובה: לנוחיותכם, קוד למחלקות Node ו- :Linked_list class Node(): def init (self, val): self.value = val self.next = None class Linked_list(): def init (self, seq=none): self.next = None self.len = 0 if seq!= None: for x in seq[::-1]: self.add_at_start(x) def add_at_start(self, val): ''' add node with value val at the list head ''' p = self tmp = p.next p.next = Node(val) p.next.next = tmp self.len += 1 def reverse(self): prev = None curr = self.next while curr!=none: tmp = curr.next curr.next = prev prev = curr curr = tmp self.next = prev 12
דף למקרה חירום 13