פרק שלישי

לולאת while

תוכן העניינים

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

age = input(‘Insert age; -1 to stop: ‘)

while age != ‘-1’:

    print(age)

    age = input(‘Insert age; -1 to stop: ‘)

 

(1) מבוא

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

(2) הֶקשרים, ייעוד וכתיבה

נעיין בחלק הראשון של תכנית עלויות הספרים שעסקנו בה בשני הפרקים הקודמים:

price1 = float(input(‘Price of book 1: ‘))

price2 = float(input(‘Price of book 2: ‘))

price3 = float(input(‘Price of book 3: ‘))

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

כשאנו רוצים לבצע הוראה או סדרת הוראות כל עוד מתקיים תנאי מסוים;

ואיננו יודעים מראש מתי התנאי יפסיק להתקיים – בייחוד כיוון שקיום או אי-קיום התנאי תלויים בקלט מאת המשתמשת; 

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

נשתמש בלולאת while. 

הדבר הראשון שעלינו לקבוע כשאנו באים להשתמש בלולאת while הוא מה יהיה התנאי שקיומו יגרום לנו להמשיך לבצע את הלולאה; נכנה תנאי זה בשם ‘תנאי הלולאה’. בדוגמה שלנו נוכל לקבוע למשל שנמשיך לקלוט עלויות ספרים כל עוד המשתמשת לא מכניסה את התו ‘q’ (קיצור של ‘quit’ ומחרוזת שבבירור אינה מייצגת עלות של ספר). האלגוריתם ייראה כך: 

(1) נקלוט מחרוזת המייצגת מספר עשרוני (עלות ספר) או המחרוזת ‘q’;

      מחרוזת זו תוצב במשתנה s

(2) כל עוד s != ‘q’a

(2.1)        נמיר את s למספר עשרוני ונשמור את המספר במשתנה price

(2.2)        נקלוט מחרוזת המייצגת מספר עשרוני (עלות ספר) או את המחרוזת ‘q’; 

                 מחרוזת זו תוצב במשתנה s

לאלגוריתם זה יש היבט עקרוני שחשוב מאוד לתת עליו את הדעת: בסוף ביצוע צעד 2.2 לא נמשיך להוראות הבאות בתכנית (בדיקה אם תינתן הנחה וכו’), אלא נחזור לצעד 2 ונבדוק שוב את המחרוזת s האחרונה שנקלטה! זה הבדל מהותי מהזרימה בכל התכניות שראינו עד כה. בתכניות הללו כִיווּן הזרימה היה “מלמעלה למטה” – כלומר התחלנו מהוראה אחת, עברנו להוראה הבאה אחריה, וממנה להוראה הבאה אחריה, וכן הלאה. כיוון כללי זה נשמר גם אם ביצוע הוראה מסוימת הותנה בקיום תנאי מסוים (באמצעות הוראת if). לעומת זאת בלולאת while ככלל אנו מפסיקים לבצע את ההוראות בגוף ה-while אך ורק כאשר תנאי הלולאה אינו מתקיים (יש יוצאים מכלל זה, ונדון בהם להלן). 

גם שימו לב שצעדים 2.1 ו-2.2 לא יבוצעו אפילו פעם אחת אם כבר בצעד 1 המשתמשת תכניס ‘q’.

התרשים הזה ממחיש את זרימת האלגוריתם: 

 

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

# 1

s = input(‘Please enter book price: ‘)

# 2

while s != ‘q’: 

    # 2.1 

    price = float(s) 

    # 2.2

    s = input(‘Please enter book price: ‘)

print(‘Last string entered is: ‘, s)

 

שימו לב להזחה: שתי ההוראות בגוף לולאת ה-while מוזזות ימינה לעומת השורה הראשונה במבנה while. 

נקודה חשובה נוספת נוגעת לתנאי הלולאה. על פי רוב בלולאת while מהסוג שאנו עוסקים בו כאן – דהיינו לולאת while שמספר סיבוביה אינו ידוע מראש – יופיע בתנאי הלולאה משתנה שערכו בלתי קבוע לאורך התכנית, וששינוי הערך המוצב בו יכול לשנות את תוצאת בדיקת התנאי. כאן המשתנה הזה הוא s. ברור כי אם נכנסנו ללולאה וביצענו את ההוראות בגוף הלולאה, הערך במשתנה s גרם לכך שתוצאת בדיקת התנאי לפני הכניסה לגוף הלולאה הייתה True (כלומר המשתמשת הכניסה מחרוזת שאינה ‘q’). ואולם התוצאה של התנאי הנבדק יכולה להשתנות בסיבובים נוספים של הלולאה: אם תיקלט המחרוזת ‘q’ ותוצב במשתנה s, תוצאת בדיקת התנאי תהיה False והלולאה תסתיים. 

הנה דוגמה להרצת התכנית:

בהרצה זו המשתמשת הכניסה עלויות של 4 ספרים. בפעולת ההכנסה החמישית היא הכניסה את המחרוזת ‘q’. מחרוזת זו הודפסה והתכנית כולה הסתיימה. 

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

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

נתחיל בהגדרת משתנה ואתחולו ל-0. בסוף התכנית יכיל משתנה זה את סכום עלויות הספרים. בכל פעם שהמחרוזת שהמשתמשת מכניסה היא עלות ספר (ולא ‘q’), נוסיף את העלות הזאת לסכום ההולך ומצטבר במשתנה. הזרימה תראה אפוא כך:  

והאלגוריתם ישוכתב באופן זה:

(1) sumPrices = 0

(2) קליטת מחרוזת המייצגת מספר עשרוני (עלות ספר) או המחרוזת ‘q’; 

     מחרוזת זו תוצב במשתנה s

(3) כל עוד s != ‘q’a

(3.1)     המרת s לערך מסוג float והצבתו במשתנה price

(3.2)      הוספת price לסכום המצטבר במשתנה sumPrices

(3.3)      קליטת מחרוזת המייצגת מספר עשרוני (עלות ספר) או המחרוזת ‘q’; 

              מחרוזת זו תוצב במשתנה s

הוספנו כאן משתנה שאינו נזכר בתרשים הזרימה לעיל: זה המשתנה price. אפשר לאחד את צעדים 3.1 ו-3.2 לצעד אחד ולהימנע משימוש בו. 

והנה לפניכם המימוש. גם הפעם הוספנו הוראת הדפסה לאחר הלולאה: היא מודיעה על סכום העלויות. 

# 1 

sumPrices = 0 

# 2

s = input(‘Please enter book price: ‘)

# 3

while s != ‘q’: 

    # 3.1 

    price = float(s)

    # 3.2 

    sumPrices = sumPrices + price

    # 3.3 

    s = input(‘Please enter book price: ‘)

# 4

print(‘Sum of prices is: ‘, sumPrices)

תנו דעתכם כי בשעת ביצועו של צעד 3.1, כלומר ההמרה של המחרוזת שהכניסה המשתמשת לערך מסוג מספר עשרוני, אנו יודעים בוודאות שהמחרוזת שנקלטה אינה ‘q’, ואנו מניחים שהיא מחרוזת המייצגת מספר עשרוני, כגון ‘52.7’ (ככלל אנו מניחים כי המשתמשת ממלאת בנאמנות אחר ההנחיות שהיא מקבלת מהתכניות שאנו כותבים). 

הנה דוגמה להרצת הקוד: 

בהרצתה זו זרמה התכנית כך: 

• ההרצה התחילה בהגדרת המשתנה sumPrices, ואתחולו ל-0. 

• אחר כך נקלטה המחרוזת ‘82.6’. 

• כיוון שמחרוזת זו שונה מ-‘q’, בוצע גוף הלולאה פעם אחת: המחרוזת הומרה למספר העשרוני 82.6, מספר זה הוסף לסכום הנצבר במשתנה sumPrices. 

• או אז, בסופו של גוף הלולאה, נקלטה מחרוזת נוספת: ‘17.4’.

• • לאחר קליטה זו, הפעולה האחרונה בגוף הלולאה, הזרימה עברה לבדיקת תנאי הלולאה. כיוון שגם מחרוזת זו שונה מ-‘q’, בוצע גוף הלולאה פעם נוספת: המחרוזת הומרה למספר העשרוני 17.4, מספר זה הוסף לסכום הנצבר במשתנה sumPrices. 

• שוב נקלטה מחרוזת. בפעם הזאת המחרוזת שנקלטה היא ‘q’, 

• כיוון שהמשתמשת הכניסה ‘q’, לא בוצע סיבוב נוסף של הלולאה. 

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

 

לסיום נבחן היבט נוסף של הקוד האחרון. הנה הקוד שוב:

sumPrices = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    sumPrices = sumPrices + price

    s = input(‘Please enter book price: ‘)

print(‘Sum of prices is: ‘, sumPrices)

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

sumPrices = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    s = input(‘Please enter book price: ‘)

    price = float(s)

    sumPrices = sumPrices + price

print(‘Sum of prices is: ‘, sumPrices)

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

(3) תבניות בלולאת while

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

(3.1) מניה, סכום ומכפלה

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

sumPrices = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    sumPrices = sumPrices + price

    s = input(‘Please enter book price: ‘)

print(‘Sum of prices is: ‘, sumPrices)

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

    (1) הגדרת משתנה-סכימה ואתחולו ל-0

    (2) עבור כל מספר נקלט

    (2.1)     נוסיף את המספר למספר שיש במשתנה הסכימה, ונציב את התוצאה במשתנה זה 

תבנית דומה לתבנית הסכומה היא תבנית המנייה (counting). תבנית זו משמשת בכתיבת אלגוריתמים שנועדו למנות מספר בלתי ידוע של ערכים אגב קליטתם. למשל נוכל להשתמש בה בכתיבת תכנית המחשבת את מספר עלויות הספרים שנקלטו. הנה הקוד: 

counter = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    counter = counter + 1

    s = input(‘Please enter book price: ‘)

print(‘Number of prices entered: ‘, counter)

 

והנה המבנה הכללי של תבנית המניה: 

    (1) הגדרת משתנה-מונה (counter) ואתחולו ל-0

    (2) עבור כל ערך נקלט

    (2.1)        הוספת 1 למספר במשתנה המונה והצבת התוצאה במשתנה זה

האתחול של משתנה המניה ל-0 הוא הכרחי כאן, כדי שהמנייה תתבצע באופן תקין ומדויק.

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

mulPrices = 1 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    mulPrices = mulPrices * price 

    s = input(‘Please enter book price: ‘)

print(‘Multiplication of all prices: ‘, mulPrices)

התכנית מבוססת על תבנית מכפלה:

    (1) הגדרת משתנה-הכפלה ואתחולו ל-1

    (2) עבור כל מספר נקלט

    (2.1)     נכפיל את המספר במספר שיש במשתנה ההכפלה, ונציב את התוצאה במשתנה זה

תנו דעתכם לצעד 1 – אתחול משתנה ההכפלה ל-1. לו היה מאותחל ל-0 כמו בתבנית הסכימה, תוצאת כל הכפלה והכפלה בצעד 2.1 הייתה 0! 

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

    (1) הגדרת משתנה-מונה ואתחולו ל-0

    (2) עבור כל ערך נקלט

    (2.1)     אם הערך מקיים תנאי 

    (2.1.1)        נוסיף 1 למספר במשתנה המונה ונציב את התוצאה במשתנה זה

והנה תכנית הבנויה על תבנית המניה המעודכנת: 

counter = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    if price > 50: 

        counter = counter + 1

    s = input(‘Please enter book price: ‘)

print(‘Number of prices greater than 50: ‘, counter)

 

תכנית זו מחשבת את מספר עלויות הספרים הגדולות מ-50 ש”ח.

(3.2) איתור ערכי מקסימום ומינימום

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

s = input(‘Please enter book price: ‘)

maxPrice = float(s) 

while s != ‘q’: 

    price = float(s)

    if maxPrice < price: 

        maxPrice = price

    s = input(‘Please enter book price: ‘)

print(‘Maximum price is: ‘, maxPrice)

נתחיל ונתמקד בהוראה השנייה בתכנית. הוראה זו ממירה את המחרוזת הנקלטת בהוראה הראשונה למספר עשרוני. כאמור התכנית מניחה שנקלטת לפחות עלות ספר אחת. לכן אפשר להניח כי ההמרה תעבור בהצלחה. הנחה נוספת בקוד הזה, והיא ספציפית לתבנית הנדונה כאן, היא שלפני הכניסה ללולאה המספר הראשון שנקלט – והיחיד עד כה – הוא העלות המקסימלית. אנו שומרים את המספר הזה במשתנה ייעודי: maxPrice, והזרימה ממשיכה ללולאת ה-while. בכל סיבוב של הלולאה, אם נקלטת עלות של ספר (ולא מחרוזת ‘q’), והעלות הזאת גבוהה מהעלות המקסימלית הנוכחית, העלות הנשמרת במשתנה maxPrice מתעדכנת בהתאם. בגמר הלולאה המשתנה maxPrice מחזיק את העלות הגבוהה ביותר מכל העלויות שנקלטו. 

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

    (1) הגדרת משתנה-ערך-מקסימלי ואתחולו לערך הראשון שנקלט

    (2) עבור כל ערך נקלט

    (2.1)      אם הערך הנקלט גדול מהערך במשתנה-הערך-המקסימלי 

    (2.1.1)              נציב במשתנה זה את הערך הנקלט 

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

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

    (1) נגדיר משתנה ונאתחל אותו לערך הראשון הנקלט

    (2) עבור כל ערך הנקלט בלולאה

    (2.1)      אם ערך זה קטן מהערך במשתנה שהוגדר בצעד 1 

    (2.1.1)              נציב במשתנה הזה את הערך הנקלט

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

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

(4) קינון while ב-while

ממש כשם שהוראת if אחת יכולה לקנן בהוראת if אחרת, לולאת while אחת יכולה לקנן בתוך לולאת while אחרת. נדגים את הדברים. 

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

sumPrices = 0 

s = input(‘Please enter book price (non-negative): ‘)

while s != ‘q’: 

  price = float(s)

  while price < 0:  

    price = float(input(‘Enter again: ‘))

  sumPrices = sumPrices + price

  s = input(‘Please enter book price: ‘)

print(‘Sum of prices is: ‘, sumPrices)

לפנינו תכנית שיש בה לולאת while פנימית המקננת בלולאת while חיצונית. 

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

הנה דוגמת הרצה של התכנית:

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

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

sumPrices = 0 

s = input(‘Please enter book price (non-negative): ‘)

while s != ‘q’: 

  while s != ‘q’ and float(s) < 0:

    s = input(‘Enter again: ‘)

  if s != ‘q’: 

    price = float(s)

    sumPrices = sumPrices + price

    s = input(‘Please enter book price (non-negative): ‘)

print(‘Sum of prices is: ‘, sumPrices)

להחלטה לאפשר למשתמשת להכניס את המחרוזת ‘q’ בתוך הלולאה הפנימית, הייתה השפעה ישירה על התנאי של הלולאה הפנימית: כאן נמשיך ונקלוט ערך מהמשתמשת כל עוד המחרוזת שהיא מכניסה אינה ‘q’ וגם המחרוזת שהיא מכניסה היא מספר שלילי. שימו לב לסדר כתיבת התנאים: אם המחרוזת שהוכנסה בתוך הלולאה הפנימית היא ‘q’ נרצה להימנע מביצוע ההמרה של המחרוזת למספר עשרוני, כיוון שהמרה זו תוליך לשגיאה. כפי שראינו (פרק שני, סעיף 3), בביטוי לוגי מורכב המבטא יחס מסוג “וגם”, אם תוצאת הביטוי המופיע משמאל למילה and היא False, לא נבדק הביטוי המופיע לאחר המילה. כך נמנעת השגיאה הנוצרת מנסיון להמיר ‘q’ למספר עשרוני. 

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

הנה דוגמת הרצה לקוד המשוכתב: 

כאן, לאחר שהמשתמשת הכניסה את המספר 83.6-, בוצעה הלולאה הפנימית ובה הכניסה המשתמשת את המחרוזת ‘q’. כפי שאפשר לראות הדבר גרם לסיום הלולאה החיצוני והדפסת סכום העלויות.

(5) כתיבת לולאה אינסופית

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

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

while 7 == 7: 

    s = input(‘Please enter book price: ‘)

כמו כל לולאת while גם הלולאה בקוד זה תרוץ כל עוד מתקיים התנאי שהיא בודקת. התנאי כאן –     7 == 7  – אינו תלוי בקלט שמכניסה משתמשת: ככלל אין מעורב בתנאי הזה משתנה ששינוי בערך בו יכול לגרור שינוי בתוצאה של בדיקת התנאי. תוצאת התנאי כאן היא קבועה: True. בניסוח חילופי לזה: שלא כמו בדוגמות ללולאות while שראינו קודם, בגוף הלולאה כאן אין הוראה כלשהי (למשל קליטה של מחרוזת ‘q’) היכולה לשנות את תוצאת הבדיקה של תנאי הלולאה, ולכן התוצאה הזאת נשארת קבועה, True, והתנאי הנבדק תמיד מתקיים. ואם התנאי תמיד מתקיים, ההוראות בגוף ה-while יתבצעו (עקרונית) אינסוף פעמים. 

אם ברצוננו לכתוב לולאת while אינסופית, אין צורך “להמציא” ביטוי שתוצאתו היא תמיד True, כגון 7 == 7. כל שעלינו לעשות הוא לציין במפורש את הערך True בתור “הביטוי הלוגי” שהוא תנאי הלולאה, כך: 

while True: 

    s = input(‘Please enter book price: ‘)

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

(6) עצירת לולאה – ההוראה break

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

        (1) sumPrices = 0

        (2) כל עוד True

        (2.1) קליטת מחרוזת s

        (2.2)       אם s != ‘q’a

        (2.2.1)         המרת s למספר עשרוני והצבת המספר במשתנה price

        (2.2.2)         הוספת price לסכום המצטבר במשתנה sumPrices

שימו לב לצעד 2: בסעיף הקודם ראינו מה פירוש ‘כל עוד True’ – הוא מורה על לולאת while אינסופית. מכאן שעוד טרם מימשנו את האלגוריתם אנו יכולים לדעת מיד כי עקרונית סדרת ההוראות בגוף הלולאה – החל מצעד 2.1 עד צעד 2.2.2 – תתבצע אינסוף פעמים, וכי אם נוסיף הוראות לאחר לולאת ה-while, הזרימה לעולם לא תגיע אליהן. מובן שזו בעיה ועוד מעט נראה כיצד לפתור אותה. 

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

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

# 1 

sumPrices = 0 

# 2

while True: 

  # 2.1 

  s = input(‘Please enter book price: ‘)

  # 2.2

  if s != ‘q’: 

    # 2.2.1 

    price = float(s)

    # 2.2.2 

    sumPrices = sumPrices + price

print(‘Sum of prices is: ‘, sumPrices)

נעקוב אחר ביצוע אפשרי של הקוד: 

• נניח שבסיבוב הראשון של הלולאה, המשתמשת מכניסה את המחרוזת ‘14.6’. כיוון שהמחרוזת הזאת שונה מהמחרוזת ‘q’, היא מומרת למספר העשרוני 14.6 והמספר הזה מוסף לסכום ההולך ומצטבר במשתנה sumPrices. 

• או אז נבדק שוב תנאי הלולאה. כיוון שהוא True, מתבצע סיבוב נוסף. 

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

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

הנה כך נשכתב את האלגוריתם באמצעות שימוש בהוראה break:

        (1) sumPrices = 0

        (2) כל עוד True

        (2.1) קליטת מחרוזת s

        (2.2)       אם s == ‘q’

        (2.2.1)            ביצוע ההוראה break

        (2.3)      המרת s למספר עשרוני והצבת המספר במשתנה price

        (2.4)      הוספת price לסכום המצטבר במשתנה sumPrices

        (3) הודעה בדבר הסכום הכולל של העלויות

נעקוב אחר ביצוע אלגוריתם זה:

• שוב נניח כי בסיבוב הראשון של הלולאה המשתמשת מכניסה את המחרוזת ‘14.6’. 

• הבדיקה בצעד 2.2 תגלה שהמחרוזת שהוכנסה אינה ‘q’. לכן לא תתבצע ההוראה break. המחרוזת תומר למספר העשרוני 14.6, והמספר הזה יוסף לסכום ההולך ומצטבר sumPrices. 

• או אז ייבדק שוב תנאי הלולאה. כיוון שהוא True, יתבצע סיבוב נוסף של הלולאה. 

• ושוב נניח שבסיבוב השני המשתמשת הכניסה את המחרוזת ‘q’. 

• הבדיקה בצעד 2.2 תגלה שהמחרוזת שהוכנסה היא ‘q’. לכן תתבצע ההוראה break, הלולאה תיקטע מיד והזרימה תעבור לצעד 3, הוראת ההדפסה הבאה לאחר ללולאה. 

הנה מימוש של האלגוריתם המשוכתב:

# 1 

sumPrices = 0 

# 2

while True: 

  # 2.1 

  s = input(‘Please enter book price: ‘)

  # 2.2

  if s == ‘q’: 

    break 

  # 2.3

  price = float(s)

  # 2.4

  sumPrices = sumPrices + price

# 3

print(‘Sum of prices is: ‘, sumPrices)

והנה תיעוד של הרצת התכנית הזאת. כאן המשתמשת מכניסה את המחרוזות ‘82.6’, ‘17.4’ ו-‘q’, בסדר זה. 

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

(7) דילוג על סיבוב – ההוראה continue

יש הוראות נוספות שאפשר להוסיפן ללולאה ולהשפיע על זרימתה. נמנות עמן ההוראה pass וההוראה else – בשתיהן לא נעסוק בספר זה – והוראה שלישית, continue, העומדת במרכז הסעיף הנוכחי. 

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

while True: 

  s = input(‘Please enter book price: ‘)

  if s == ‘52.3’: 

    continue 

  print(s) 

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

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

 

sumPrices = 0 

while True: 

  s = input(‘Please enter book price: ‘)

  if s == ‘q’: 

    break 

  price = float(s)

  sumPrices = sumPrices + price

print(‘Sum of prices is: ‘, sumPrices)

 

הנה נסיון ראשון לשכתב את התכנית באופן שתימנע מהשימוש ב-break:

sumPrices = 0 

proceed = True

while proceed == True: 

  s = input(‘Please enter book price: ‘)

  if s == ‘q’:

    proceed = False

  price = float(s)

  sumPrices = sumPrices + price

print(‘Sum of prices is: ‘, sumPrices)

נקודת המוצא להבנת השכתוב טמונה בתנאי הלולאה. התנאי כבר אינו הערך הבוליאני True, אלא ביטוי לוגי זה:

proceed == True

יש כאן בדיקה: האם המשתנה proceed מכיל את הערך True? המשתנה ששמו כאן proceed  (אפשר לתת לו שם אחר) הוא חידוש בתכנית. הוא מוגדר לפני הלולאה. הוא מאותחל לערך True, ומכאן ולפי תנאי הלולאה ברור שניכָנס לגוף הלולאה לפחות פעם אחת. כמו בתכנית הקודמת, גם בתכנית הזאת ההוראות הראשונות בגוף הלולאה קולטות מחרוזת מהמשתמשת ובודקות אם המחרוזת שנקלטה היא ‘q’. ואולם הפעם אם המחרוזת שנקלטה היא ‘q’ אין אנו מזנקים אל מחוץ ללולאה באמצעות ההוראה break. במקום זה, אנו מעדכנים – וזו הנקודה הקריטית בקוד – את ערך המשתנה proceed: במקום True הוא יחזיק False. כיוון שהצעדים הבאים בגוף הלולאה אינם נוגעים בערך שיש במשתנה proceed, ברור כי כשנשוב ונבדוק את תנאי הלולאה, נמצא כי הוא כבר אינו מתקיים והלולאה תסתיים, כנדרש. 

משתנה מסוגו של המשתנה proceed מכונה “דגל” (flag). הערך בו מאותת לנו אם יש לבצע או להפסיק לבצע פעולה או פעולות מסוימות. הוא מועיל במניעת השימוש בהוראה break. במקום לעצור לולאה ברגע שמתקיים תנאי מסוים, אנו מגדירים מראש משתנה המחזיק את הערך הבוליאני True, ומגדירים את תנאי הלולאה בתור בדיקה של הערך במשתנה הזה. ברגע שמתברר שיש להפסיק את הלולאה, אנו משנים את ערך המשתנה ל-False, וגורמים להוראה להיפסק בפעם הבאה שייבדק התנאי. 

על אף שהשימוש במשתנה proceed מונע את הצורך בשימוש ב-break, התכנית עדיין אינו מתאימה לגמרי לצרכינו. הסיבה קשורה בהוראת ההמרה של המחרוזת שנקלטה למספר עשרוני. כאמור שלא כמו בתכנית הקודמת, בתכנית הזאת לא נסיים מיד את הלולאה כאשר המשתמשת תכניס את המחרוזת ‘q’ ותאותת על רצונה לסיים את הכנסת הנתונים. במקום זה נמשיך לבצע את ההוראות בסיבוב הנוכחי של הלולאה, ובייחוד את הוראת ההמרה. הנסיון להמיר את המחרוזת האחרונה שהוכנסה, ‘q’, למספר עשרוני. תגרום לשגיאה! התכנית היא אפוא בלתי תקינה. כיצד נתקנה? 

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

sumPrices = 0 

proceed = True

while proceed == True: 

  s = input(‘Please enter book price: ‘)

  if s == ‘q’:

    proceed = False

    continue 

  price = float(s)

  sumPrices = sumPrices + price

print(‘Sum of prices is: ‘, sumPrices)

כאן, לאחר שמתברר שהמשתמשת הכניסה את המחרוזת ‘q’, והערך במשתנה proceed מוחלף מ-True ל-False (כדי לעצור את הלולאה בסוף סיבובה הנוכחי), ההוראה continue מבוצעת. ביצועה פירושו שאין מתבצעות כל ההוראות הבאות אחריה בגוף הלולאה – כאן: ההמרה של המחרוזת למספר עשרוני והוספת המספר לסכום ההולך ומצטבר. כיוון שכך נמנעת השגיאה בנסיון להמיר את המחרוזת ‘q’ למספר עשרוני, ואנו עוברים ישירות לבדיקת תנאי הלולאה. כיוון שהמשתנה proceed מכיל עכשיו False, מסתיימת הלולאה ואנו עוברים להוראות הבאות אחריה בתכנית. 

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

s = input(‘Please enter book price: ‘)

maxPrice = float(s) 

while s != ‘q’: 

    price = float(s)

    if maxPrice < price: 

        maxPrice = price

    s = input(‘Please enter book price: ‘)

print(‘Maximum price is: ‘, maxPrice)

 

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

maxPrice = ‘NA’

proceed = True 

while proceed: 

    s = input(‘Please enter book price, q to stop: ‘)

    if s == ‘q’: 

        proceed = False 

        continue 

    price = float(s)

    if maxPrice == ‘NA’: 

        maxPrice = price

    else: 

        if maxPrice < price: 

            maxPrice = price

print(maxPrice)

מבחינה עקרונית המנגנון המשמש בזרימת הלולאה בתכנית הזאת ובקטיעתה זהה לזה שעליו נסמכה התכנית הקולטת את עלויות הספרים: גם כאן אין נקלטים נתונים לפני תחילת ביצוע הלולאה; גם כאן הלולאה ‘מסתובבת’ כל עוד הערך בדגל proceed שווה ל-True; גם כאן הערך במשתנה הזה מוחלף ב-False אם המשתמשת מאותתת שהיא רוצה להפסיק את הכנסת הנתונים; וגם כאן כשהיא מאותתת זאת מבוצעת ההוראה continue, אין מבוצעות ההוראות הבאות אחריה בגוף הלולאה, הזרימה עוברת לבדיקת תנאי הלולאה, והלולאה נקטעת. 

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

(8) לולאת while שמספר סיבוביה ידוע מראש

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

i = 1

while i < 5:

    print(‘something’) 

    i = i + 1 

print(‘The End’)

מבחינת התחביר של הקוד ומבחינת מבנה הלולאה המופיע בקוד, אין כל צל צלו של ספק: לפנינו לולאת while. הלולאה מבצעת הוראות כל עוד מתקיים תנאי. בייחוד התנאי בודק אם מספר המוצב במשתנה i קטן מ-5. המספר שיש במשתנה לפני הכניסה ללולאה הוא 1, ולכן בבדיקת התנאי הראשונה נמצא שהוא מתקיים וגוף הלולאה מתבצע. בגוף הלולאה יש הוראה המשנה את הערך המוצב במשתנה i – היא מגדילה אותו ב-1 – וכך יכולה לשנות את תוצאת הבדיקה של תנאי הלולאה ולהוליך לסיום הלולאה. אם כן עד לסוף הלולאה יקבל המשתנה i את הערכים האלה

1, 2, 3, 4, 5

1 הוא ערך האתחול, ו-5 הוא הערך האחרון המוצב במשתנה i – הצבתו מביאה לסיום הלולאה ומעבר להוראת ההדפסה הבאה לאחר הלולאה. 

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

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

sumGrades = 0 

gradesNum = 5

i = 1

while i <= gradesNum:

    grade = input(‘Please enter grades: ‘)

    sumGrades = sumGrades + float(grade)  

    i = i + 1

print(‘Average grade is ‘, sumGrades / gradesNum)

לפני הלולאה מוגדר המשתנה sumGrades: הוא מאותחל ל-0, ועד גמר ביצוע הלולאה הוא יכיל את סכום כל הציונים שנקלטו. חוץ ממנו מוגדר מספר הציונים שיש לקלוט – חמישה – ומספר זה מוצב במשתנה gradesNum. כמו כן מאותחל המשתנה i ל-1. הגדרות אלה, תנאי הלולאה – יש להמשיך בלולאה כל עוד המספר במשתנה i קטן מ-5 או שווה ל- 5 –  והשינוי של הערך במשתנה i – בכל סיבוב הוא גדל ב-1 – כל אלה קובעים שיהיו ללולאה בדיוק חמישה סיבובים. בכל סיבוב נקלט ציון בתור מחרוזת, הוא מומר למספר עשרוני, והמספר הזה מוסף לסכום המצטבר במשתנה sumGrades. לאחר שביצוע הלולאה מסתיים מחושב ממוצע הציונים – סכומם חלקי מספר הציונים שנקלטו – והממוצע הזה מודפס. 

נקודה חשובה שיש לתת עליה את הדעת בקטע הקוד הזה היא שהוא ירוץ גם עבור ערכים תקינים אחרים שיוצבו במשתנה gradesNum . למשל אם היינו רוצים לקלוט 10 ציונים ולא 5, כל שהיה עלינו לשנות בקוד הוא ערך האתחול של המשתנה הזה: במקום 5 הוא היה 10. 

בפייתון אפשר אפוא לממש את המבנה ‘יש לבצע מספר פעמים מוגדר” באמצעות לולאת while. ואולם על פי רוב מימושו נעשה באמצעות סוג אחר של לולאה: לולאת for, בלולאה מסוג זה נדון בפרק הבא.

(9) מעקב אחר ביצוע קוד

האורך והמוּרכבות ההולכים וגדלים של התכניות שאנו כותבים יכולים להקשות על הבנתן. הקושי קיים בייחוד אם איננו יכולים להריץ את התכנית, אך גם אם נריץ אותה ונבקש להבין מה מתרחש בשלב כזה או אחר של ביצועה. כלי מועיל בהבנת זרימה של תכנית ושל תוצאות החישובים וההדפסות בה הוא טבלת מעקב (flow chart). לפי המקובל, טבלת מעקב מתעדת שינויים המתרחשים בהרצת אלגוריתם או תכנית עבור קלט מסוים, ובייחוד: 

• שינויים בערכים המוצבים במשתנים 

• שינויים בתוצאות החישובים של ביטויים לוגיים

• וגם תוצאות של החישוב העיקרי המחושב בתכנית (או האלגוריתם) או הפלט שלה. 

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

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

sumPrices = 0 

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    sumPrices = sumPrices + price

    s = input(‘Please enter book price: ‘)

print(sumPrices) 

נמלא את הטבלה עבור ההרצה הזאת:

 

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

 

הטבלה מכילה עמודות נפרדות לערכים במשתנה sumPrices ובמשתנה s לפני הסיבוב הבא ובסוף הסיבוב.

הבדיקה הראשונה של תנאי הלולאה

• בשעת הבדיקה הראשונה של תנאי הלולאה מוצב במשתנה sumPrices ערך האתחול שלו, 0. 

בנוסף הערך הראשון המוצב במשתנה s הוא המחרוזת הראשונה שהמשתמשת הכניסה, כלומר ’82.6’.

מכאן שתוצאת חישוב תנאי הלולאה היא True, ומכאן שמתבצע גוף הלולאה בפעם הראשונה.  

ביצוע ראשון של גוף הלולאה

• בגוף הלולאה מחושב סכום העלויות המעודכן וסכום זה מוצב במשתנה sumPrices.

ובסוף הסיבוב נקלטת מחרוזת חדשה מהמשתמשת ומחרוזת זו מוצבת במשתנה s במקום המחרוזת שהייתה בו בתחילת הסיבוב. 

בסוף הסיבוב הראשון כבר ידוע לנו מה הערכים המוצבים במשתנים s ו-sumPrices בשעת הבדיקה הבאה של תנאי הלולאה, ואפשר למלאם מיד.

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

ביצוע שני של גוף הלולאה

• שוב הסיבוב מתחיל בעדכון סכום העלויות.

ובשלב הבא נקלטת מחרוזת חדשה מהמשתמשת.

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

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

(10) סיכום

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

price1 = float(input(‘Price of book 1: ‘))

price2 = float(input(‘Price of book 2: ‘))

price3 = float(input(‘Price of book 3: ‘))

לעומת זאת, בתכניות שכתבנו באמצעות לולאת while כתבנו את החלק כך (במבנה ‘קליטה לפני לולאה’):

s = input(‘Please enter book price: ‘)

while s != ‘q’: 

    price = float(s)

    s = input(‘Please enter book price: ‘)

השימוש בלולאת while שיפר את התכנית שכתבנו בפרק הקודם: עתה כבר לא היינו מוגבלים לקליטת שלוש עלויות ספרים בלבד אלא יכולנו לקלוט מספר כלשהו של עלויות ספרים. ואולם לשיפור הזה יכול להיות גם מחיר. כיוון שאנו לא יודעים כמה עלויות יוכנסו, אין לנו דרך להגדיר מראש משתנים שבהם ישמרו כל העלויות, כפי שעשינו בתכנית הישנה – בתכנית ההיא הגדרנו את המשתנים price2, price1, ו-price3 לצורך כך. כיוון שהתכנית החדשה, זו המשתמשת בלולאת while, לא שומרת את עלויות הספרים במשתנים, ממילא לאחר סיום הלולאה אין לנו גישה לעלויות שהוכנסו במהלך ביצועה, חוץ מהעלות האחרונה (הנשמרת במשתנה price). זה עשוי להיות חסרון, אם נרצה להפעיל פעולות מסוימות על העלויות שנקלטו בגמר הלולאה. חשבו למשל על מצב עניינים זה: בגמר הלולאה נרצה לבדוק אם אחת או יותר מעלויות הספרים שהוכנסו הן גדולות מחצי סכום העלויות שחושב (וששמרנו במשתנה sumPrices). הדרך היחידה לעשות זאת באמצעות הקוד שכתבנו בפרק זה היא לבקש מהמשתמשת להכניס מחדש את כל העלויות ולהשוות כל עלות מוכנסת לחצי מהסכום שחושב. מובן שגישה כזו היא בלתי יעילה ולא רצויה. כדי להימנע ממנה נשמור את הערכים הנקלטים במבנה נתונים. מבני נתונים והשימוש בהם יעמדו במוקד דיונינו בפרקים הבאים.