לולאות

הקדמה

זהו בוקר אפלולי וגשום של יום ראשון. השמיים אפורים ואתם במיטה מתחת לפוך המפנק שלכם, מתפללים שאתם עדיין בתוך החלום המתוק ההוא.
השעה היא 7:30. השעון המעורר מנגן שוב את השיר שפעם היה האהוב עליכם, והיום מעלה בכם אסוציאציות קשות שמערבות את הטלפון החכם שלכם ופטיש כבד מאוד.
הפגישה שלכם תתקיים בשעה 9:00, ואתם יודעים בוודאות שתספיקו להגיע בזמן אם תתעוררו בשעה 8:00 ותמהרו מאוד.
היד מושטת לכפתור ה"נודניק" שיפעיל שוב את השעון המעורר שלכם בעוד 10 דקות. ועוד פעם. ושוב.

אם נתאר את האלגוריתם שלפיו פעלתם, נוכל להגיד כך:
כל עוד השעון מצלצל, והשעה היא לפני 8:00, לחץ על כפתור הנודניק בשעון.
נצייר את דרך הפעולה שלכם:

תרשים הזרימה של שעון מעורר בבוקר מדכא.

תרגול: נסו לשרטט אלגוריתם להטלת 6 בקובייה.
כל עוד לא התקבל 6 בקובייה, הטילו את הקובייה מחדש.
כשקיבלתם 6 בקובייה, פִּצְחוּ בצהלולים בקול גדול.

הגדרה

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

  1. אתחול הסביבה – הגדרת ערכים שבהם נשתמש בלולאה.
  2. תנאי הלולאה – ביטוי בוליאני. כל עוד הביטוי הזה שקול ל־True, גוף הלולאה ימשיך להתבצע.
  3. גוף הלולאה – הפעולות שיקרו בכל פעם שתנאי הלולאה מתקיים.
  4. צעד לקראת סוף הלולאה – הליך שמבטיח שהלולאה תסתיים.

במקרה שלנו:

  1. שלב אתחול הסביבה כולל:
    1. הגדרת הצלצול הראשוני ל־7:30.
    2. הגדרת שעת ההשכמה הסופית ל־8:00.
    3. הגדרת ה"נודניק" לצלצל בעוד 10 דקות.
  2. תנאי הלולאה יכול להיות השעון המעורר פועל וגם השעה היא לפני שעת ההשכמה הסופית.
  3. גוף הלולאה יכול להיות לחיצה על הנודניק וחזרה לישון.
  4. צעד לקראת סוף הלולאה הוא הזמן שעבר בין צלצול לצלצול, שמקרב אותנו לשעה 8:00.

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

חשוב!
פתרו לפני שתמשיכו!

דוגמאות

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

לפניכם כמה דוגמאות ללולאות:

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

תרגול: מצאו את 4 הרעיונות המרכזיים שעליהם דיברנו בכל אחת מהדוגמאות.

כתיבת לולאה

בסרט המוזיקלי Hans Christian Andersen מ־1952, מופיע השיר Inchworm, ושרים בו כך:

Two and two are four
Four and four are eight
Eight and eight are sixteen
Sixteen and sixteen are thirty-two

זה הזמן לכתוב Inchworm משלנו.
נשרטט איך כתיבת לולאה עבור שיר שכזה תיראה:

תרשים זרימה שמתאר את התוכנית שנבנה להדפסת השיר Inchworm.

במקרה שלנו:

  1. שלב אתחול הסביבה הוא השלב שבו נגדיר משתנה עם הערך הראשון להדפסה: 2.
  2. תנאי הלולאה בודק אם אנחנו בשורה האחרונה של השיר או לפניה.
  3. גוף הלולאה כולל:
    1. הגדרת משתנה שמכיל את סכום שני המספרים.
    2. הדפסת שורה בשיר לפי הערך שחישבנו.
  4. הצעד לקראת סוף הלולאה במקרה שלנו הוא קידום המשתנה שמכיל את המספר הנוכחי.

טוב, זה הזמן לקצת קוד, לא?


In [ ]:
current_number = 2
while current_number <= 16:
    twice_number = current_number + current_number
    print(f"{current_number} and {current_number} are {twice_number}")
    current_number = twice_number

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

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

2 and 2 are 4

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


In [ ]:
current_number = 2

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


In [ ]:
while current_number <= 16:

אחרי שכתבנו את התנאי, זה הזמן לכתוב מה אנחנו רוצים שיתבצע בכל פעם שהתנאי יתקיים.
החלק הזה נקרא "גוף הלולאה", וכל הרצה שלו נקראת "אִיטֶרַצְיָה", או בעברית, "חִזְרוּר".

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


In [ ]:
twice_number = current_number + current_number

נדפיס את השורה עם הפרטים שיצרנו:


In [ ]:
print(f"{current_number} and {current_number} are {twice_number}")

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


In [ ]:
current_number = twice_number

סיכום ביניים

כפי שראינו, לולאת while משתמשת בביטוי בוליאני כדי להחליט אם להריץ קוד מסוים.
היא בודקת אם הביטוי הבוליאני שקול ל־True, ואם כן, היא מריצה את קטע הקוד בגוף הלולאה.
כל עוד הביטוי הבוליאני המופיע ליד המילה while שקול ל־True, גוף הלולאה ימשיך לרוץ.
כשהביטוי יהפוך להיות שקול ל־False, הלולאה תפסיק את ריצת הקוד בגוף הלולאה, והקוד שאחריה ימשיך לרוץ.

הרעיון של while מקביל ל־if שגופו רץ וחוזר לראש התנאי פעם אחר פעם, עד שהביטוי הבוליאני שבראש התנאי שקול ל־False.

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

חשוב!
פתרו לפני שתמשיכו!

דוגמאות נוספות

תרגילים חשבוניים

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


In [ ]:
def sum_positive_numbers(max_number):
    total = 0
    first_number = 1
    while first_number <= max_number:
        total = total + first_number
        first_number = first_number + 1
    return total
    
user_number = int(input("Please enter a number: "))
print(sum_positive_numbers(user_number))

הגדירו את 4 החלקים המופיעים בלולאה המופיעה בקוד מעלה.
שרטטו כיצד היא עובדת.

מיקומים ברשימה

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

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


In [ ]:
def get_grades(number_of_grades):
    grades = []
    while len(grades) < number_of_grades:
        current_grade = int(input("Please enter a student grade: "))
        grades = grades + [current_grade]
    return grades


def get_highest_grade(grades):
    highest_grade = grades[0]
    current_grade_index = 1
    while current_grade_index < len(grades):
        if grades[current_grade_index] > highest_grade:
            highest_grade = grades[current_grade_index]
        current_grade_index = current_grade_index + 1
    return highest_grade


number_of_grades = int(input("How many students are there?: "))
grades = get_grades(number_of_grades)
highest_grade = get_highest_grade(grades)
print(f"The highest grade is {highest_grade}")

חשבו על דרך לממש את הקוד הזה עם לולאה אחת בלבד.

לולאה בתוך לולאה


In [ ]:
i = 1
j = 1
while i <= 10:
    line = ''
    while j <= 10:
        line = line + str(i * j) + '\t'
        j = j + 1
    print(line)
    j = 1
    i = i + 1

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

חשוב!
פתרו לפני שתמשיכו!

ניפוי שגיאות

לולאה אין־סופית

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

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

  • טעות בתנאי – אין מצב שבו הביטוי הבוליאני בראש הלולאה יהיה שקול ל־False.
  • טעות בקידום – לא בוצע צעד שיקדם את הלולאה לכיוון הסוף שלה, ועקב כך הביטוי הבוליאני נשאר שקול ל־True.

אם ליד תא במחברת שלכם מופיעה כוכבית ותאים אחרים לא יכולים לרוץ, סימן שאותו תא עדיין רץ.
אם הוא רץ זמן רב מדי, יש סיכוי שמדובר בלולאה אין־סופית. אם זה אכן המצב, בחרו בסרגל הכלים של המחברת ב־"Kernel" ואז ב־"Restart".
פעולה זו תעצור את הריצה של המחברת שלכם, ותאפשר לכם לתקן את הקוד הבעייתי ולהריץ אותו מחדש.

הנה דוגמה ללולאה אין־סופית, בתוכנה שמטרתה לספור מ־1 עד 10:


In [ ]:
i = 1
while i < 10:
    print(i)
print("End of the program")

למה הלולאה הזו אין־סופית? תקנו אותה כך שתפעל כראוי.

הלולאה לא רצה

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


In [ ]:
i = 8
while i <= 0:
    print(i)
print("End of the program")

את הלולאה הזו כתב מתכנת מפוזר במיוחד, ויש בה יותר מבעיה אחת.
מצאו את הבעיות, תקנו אותן והריצו את התוכנית.
הפלט הרצוי, משמאל לימין, הוא: 8, 4, 2, 1.

סטייה באחד

זאת טעות נפוצה מאוד, עד כדי כך שיש לה שם ואפילו ערך בוויקיפדיה!
בשגיאה מסוג "סטייה באחד" (באנגלית: "Off By One") מתכנת שוכח לטפל במקרה האחרון, או מטפל במקרה אחד יותר מדי.

נראה דוגמה:


In [ ]:
numbers = [1, 2, 3, 4]
index = 0
total = 0

while index <= len(numbers):
    total = total + numbers[index]
    index = index + 1

print(total)

מה הבעיה פה? כיצד ניתן לפתור אותה?

טיפול בבאגים של לולאות

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

  1. הדפסות לפני תחילת הלולאה – בדקו מה הערך של תנאי הלולאה, ומהם הערכים של כל אחד מהביטויים שמרכיבים אותו.
  2. הדפסות בתוך הלולאה – בדקו מה קורה בתוך גוף הלולאה בכל איטרציה. האם הערכים של המשתנים הם אלו שצפיתם? כמה פעמים רץ גוף הלולאה?
  3. הדפסות אחרי הלולאה – מה ערכם של המשתנים בסיום הלולאה? מה הערכים של המשתנים בתנאי הבוליאני? האם הלולאה סיימה את עבודה מוקדם מדי?

מונחים

אתחול
לא רק בלולאות – הגדרת משתנים לערכים התחלתיים לפני ביצוע פעולה כלשהי.
תנאי הלולאה
ביטוי בוליאני שכל עוד הוא שקול ל־True, גוף הלולאה ירוץ.
גוף הלולאה
השורות שירוצו כל עוד תנאי הלולאה מתקדם. גוף הלולאה כולל את הקידום.
קידום
ביצוע פעולה המקרבת לסיום הלולאה.
חזרור, איטרציה
הרצה של גוף הלולאה פעם אחת.
לולאה אין־סופית
לולאה שלעולם לא תפסיק לרוץ.
סטייה באחד
מצב שבו המתכנת שוכח לטפל במקרה האחרון, או מטפל במקרה אחד יותר מהדרוש. מצב זה עלול לגרום לקריסת התוכנית.
מבנה לבקרת זרימה
שם כולל למבנים בקוד שמאפשרים לנו לגרום לקוד "לקפוץ" משורה אחת לאחרת. לולאה היא סוג של מבנה לבקרת זרימה.

תרגילים

It's the final...

נתקעתם במסיבה שבה סופרים את השניות לאחור עד לכניסת השנה החדשה.
עזרו למשתתפים המבולבלים שהתחילו לספור מוקדם מדי –
קבלו את מספר השניות שנותרו עד חצות, והדפיסו עבורם את הספירה לאחור.
בסוף הספירה, הדפיסו "Happy new year!"

לדוגמה, עבור 4, הדפיסו:
4 3 2 1 Happy new year!

Play

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

*

  • *





    • * </samp>

מפענח הצפנים 2

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

לדוגמה, אם המשתמש הקיש בניסיון הראשון 0634, הדפיסו לו שרק אחת הספרות שניחש נכונה.
אם המשתמש הקיש בניסיון השני 1234, הדפיסו לו ש־3 ספרות תואמות לקוד המקורי.
אם המשתמש הקיש בניסיון השלישי 1284, הדפיסו לו ש־4 ספרות תואמות לקוד המקורי, ואז הדפיסו לו שהופעלה האזעקה.
אם המשתמש הקיש באחד הניסיונות 4812, הדפיסו שהכספת נפתחה בהצלחה וסיימו את התוכנית מייד.

השתמשו בלולאות.

כמו קולץ בישבן

נגדיר כלל: אם מספר הוא זוגי, נחלק אותו ב־2. אם מספר הוא אי־זוגי, נכפיל אותו ב־3 ונוסיף לו 1.
לפי השערת קולץ, אם ניקח מספר חיובי שלם ונשתמש עליו פעמים רבות בכלל הזה, תמיד נגיע בסופו של דבר למספר 1.
לדוגמה, אם ניקח את המספר 52, נקבל את שרשרת הפעולות הבאה:

  • 52 זוגי, ולכן נחלק ב־2. נקבל 26.
  • 26 זוגי, ולכן נחלק ב־2. נקבל 13.
  • 13 הוא אי־זוגי, ולכן נכפיל ב־3 ונוסיף 1. נקבל 40.
  • וכך הלאה.

$\ 52 \rightarrow 26 \rightarrow 13 \rightarrow 40 \rightarrow 20 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1$

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

ממש קולץ בישבן

מצאו עבור איזה מספר בין 1 ל־1,000, צריך לעשות הכי הרבה צעדים, לפי השערת קולץ, כדי להגיע ל־1.
לדוגמה, הנה הצעדים שצריך לעשות עבור כל מספר עד 5:

  • $1$
  • $2 \rightarrow 1$
  • $3 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1$
  • $4 \rightarrow 2 \rightarrow 1$
  • $5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1$

מכאן שהמספר בין 1 ל־5 שעליו יש לעשות הכי הרבה צעדים, לפי השערת קולץ, עד שמגיעים ל־1, הוא 3.