שאלה בנוגע לפונקציות

הבנתי שפונקציה שקוראת לעצמה נקראת רקורסיה. במהלך שימוש בכך בתרגילים, למרות שלפעמים זה עובד חלק, שמתי לב שבאופן שקשה לי לחזות מתי ולמה הוא יקרה, אני מקבל ארור, בנושאים משתנים (אורך רשימה, indicators וכו’). מתוך ניסוי וטעיה, ראיתי שכשאני קורא לפונקציה מתוך עצמה, אך מוסיף return לפני הקריאה, הבאגים נעלמים. אבל אני לא מצליח להבין כל כך למה שזה יהיה הכרחי. הרי קריאה לפונקציה מתוך הפונקציה אמור לתפקד כמו כל פקודה אחרת. הדבר היחיד שעולה על דעתי, הוא שרק בשימוש בreturn, הארגומנטים שקיבלו מניפולציה בתוך הפונקציה “אופסו” למצבם המקורי כשנכנסו לראשונה. מישהו יודע אם ההנחה הזאת נכונה, ואם לא, אז מה הסיבה?

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

def test(a):
    x = input('try to guess my number:)\ninsert a 2 digits number, which contains digits "1" or "2" or "3", and divided by " " ')
    if len(x) != 3 or x[1] != " ":
        print("Invalid input!")
        return test(a)
    x = x.split(" ")
    if x[0] != "1" and x[0] != "2" and x[0] != "3":
        print("Invalid input!")
        return test(a)
    if x[1] != "1" and x[1] != "2" and x[1] != "3":
        print("Invalid input!")
        return test(a)
    x_first = int(x[0])
    x_second = int(x[1])
    if x_first == a[0] and x_second == a[1]:
        print("Nice job!")
    else:
        print("you didn'nt guess!\nplease try again")
        return test(a)
        
test([1, 2])

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

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

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

מבדיקה שערכתי בגוגל, פונקציה אינה צריכה return על מנת לתפקד. זה גם הגיוני, מאחר ו return רק מסיים את פעולתה. עד שהגיעה אליו, אינה ‘מודעת’ לכך אם קיים או לא בהמשך. זה לגבי פונקציה רגילה.
לגבי ריקורסיה, על כך בדיוק שאלתי. ההיגיון אומר שאין שום צורך לקרוא לה עם RETURN. אבל אז כן קורה משהו בתקינות ההרצה שלה. ואני מנסה להבין למה בדיוק זה קורה.

אמת שפונ’ לא צריכה return כדי לתפקד.
אבל רקורסיה היא לא כמו כולן :stuck_out_tongue:

מה קורה כאשר פונ’ קוראת לעצמה ואז מהקריאה הזו קוראת שוב לעצמה ואז שוב … ושוב… ושוב…?

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

אז תחשוב על זה עוד קצת, ספציפית על המשפט האחרון בתגובה הקודמת שלי.
מתי אתה בעצם תסיים לבצע את הרקורסיה?

מן הסתם, כשההתניות שקוראות לפונקציה לא יתממשו באחת ההרצות. מספר הקריאות לא אמור להיות רלוונטי. כל קריאה לפונקציה, ולא משנה באיזה מיקום, מריץ אותה פשוט מההתחלה. איך זה קשור לענין הטכני של אופן הקריאה לפונקציה? כמו שכתבתי בשאלה, הסיבה ההגיונית היחידה שאני יכול לחשוב עליה, היא שבקריאה לפונקציה עם הארגומנטים, מכיוון שהפונקציה כבר נמצאת בהרצה, לארגומנטים יש כבר ערך שונה מהערך המקורי, מכיוון שהם עברו מניפולציות בפונקציה עד הקריאה הרקורסית. זאת הסיבה שבדוגמה שנתתי, הקפדתי על מניפולציות למיקום הפרמטרים ברשימה, ולסוג שלהם. ורק בקריאה עם return, הם ‘מתאפסים’ לערכם המקורי. ואני רוצה להבין אם זה נכון, או שיש סיבה אחרת למדוע זה קורה.

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

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

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

לייק 1

כשאתה מסיים קריאה לפונ’ רגילה, הקוד ממשיך הלאה לשורות אחרי הקריאה שלה.
כאשר אתה ברקורסיה, השורות הבאות האלה הן שוב אותה פעילות שנעשתה בפונקציה, ללא שום סימן/בדיקה צפוי שיגיד שהגעת לסוף הקוד.

@aviadamar דקורייטורים לא ממש קשורים לזה

לייק 1

אין לי מושג מה הם בכלל חח ראיתי משהו על זה באינטרנט :slight_smile:
בכל מקרה אני חושב שזה קשור לחלק הראשון של התשובה שלי

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

לייק 1

אני חושב שאנשי הסגל ידעו הכי טוב להסביר מה קורה במצב שהוא מתאר :slight_smile:
ככה נחכים כולנו

לייק 1

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

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

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

יש הרבה מאוד דברים שיכולים להתפקשש בדרך, לא נכנסתי לעומק הקוד שלך, רק מה שכן, לא כל כך הבנתי מזה “שגיאות לא קיימות” - או שיש שגיאות, או שאין שגיאות.

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

לייק 1

ניסיתי להשתמש ב pythontutor, אבל לא הצלחתי להבין ממנו איפה הבעיה. אולי זה מחוסר ניסיון שלי בקריאת שגיאות, או שהוא פשוט לא היה יעיל במקרה הספציפי הזה. בכל מקרה, כמובן שאני בתהליך למידה… :slightly_smiling_face:
ב"שגיאות לא קיימות" התכוונתי לכך שההנחה הבסיסית היתה שקריאה לרקורסיה ללא ריטרן, כשלעצמה, לא יוצרת שגיאות. ולכן, חיפשתי שגיאות בכל שאר הקוד, כשמעבר לאופן הקריאה לריקורסיה, לא היתה בו אף שגיאה. וכמובן שאני מוכן להשקעה של שעות ונהנה מזה, ומבין שזה מלמד המון

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

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

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

3 לייקים

רקורסיות זה בהחלט “מגניב” מבחינת קוד.
לפעמים קוד רקורסיבי קצר מאוד עושה דברים מדהימים.

אבל , וזה אבל גדול -
זה לא אומר שזו הדרך היעילה ביותר , בדרך כלל לא הדרך הבטוחה ביותר , ולדעתי תמיד יש לה תחליף !
אני כמעט בטוח שכל מה שאתה יכול לעשות עם רקורסיה , אתה יכול לעשות עם לולאת while !

אז נכון שאנשים מתלהבים מזה , אבל לפחות בהתחלה - אם אתה לא בטוח במה שאתה עושה , מוטב לך להיות אלוף בלולאות מאשר ברקורסיות.
אם כבר החלטת ללכת על רקורסיות , אז לפחות בהתחלה שים הרבה הוראות “פרינט” בפנים , כך תוכל לעקוב אחרי הערכים.
אני גם אוהב להשתמש ברקורסיות ב"דיפולט ארגומנטס" , זה הופך את הרקורסיה לפשוטה יותר.