פעמים רבות אנחנו מנסים לפתור בעיה, שכדי להגיע לפתרונה נידרש לעבור על כל הערכים במבנה מסוים:
מה משותף לכל הבעיות שהוצגו למעלה?
דרך ראויה לפתור אותן היא בעזרת לולאה שתעבור על כל האיברים שהוצגו בבעיה, ותבצע על כל איבר סדרת פעולות.
נכתוב בפסאודו־קוד דוגמה לפתרון הבעיה הראשונה – מציאת הגובה של התלמיד הגבוה ביותר בכיתה:
ממשו פונקציה שמקבלת רשימת גבהים של האנשים בכיתה, ומחזירה את הגובה של התלמיד הגבוה ביותר.
לדוגמה, עבור הרשימה [1.50, 1.84, 1.73, 1.51]
החזירו 1.84.
חשוב!
פתרו לפני שתמשיכו!
עד כה, אחד מהשימושים הנפוצים שעשינו בלולאת while
היה לעבור על איברים ברשימה באמצעות מיקום.
הקוד שכתבתם בתרגול למעלה, שבו הייתם צריכים לעבור על גובהי התלמידים, זה אחר זה, הוא דוגמה טובה לכך.
ייתכן שהקוד שלכם דומה לקוד שאני כתבתי לפתרון התרגיל הזה:
In [ ]:
def get_tallest(student_heights):
max_height_so_far = 0
current_student_index = 0
while current_student_index < len(student_heights):
current_student_height = student_heights[current_student_index]
if current_student_height > max_height_so_far:
max_height_so_far = current_student_height
current_student_index = current_student_index + 1
return max_height_so_far
heights = [1.50, 1.84, 1.73, 1.51]
print(get_tallest(heights))
הקוד בתא האחרון עושה את הפעולות הבאות:
student_heights[current_student_index]
ישיג את גובהו של אחד התלמידים, לפי הסדר.
הלולאה שמופיעה תעבור על מספרי התאים ברשימה ותבדוק את התוכן שלהם.
עד כה, פעמים רבות השימוש שלנו בלולאות היה לצורך מעבר על כל האיברים של iterable כלשהו.
למעשה, בדוגמה שלמעלה אנחנו מבצעים פעולה עבור כל איבר בתוך student_heights.
בפעמים שבהן אנחנו רוצים לבצע דבר מה עבור כל אחד מהאיברים במבנה כלשהו, נשתמש בלולאת for
.
נראה, לדוגמה, איך נשתמש בלולאת while
כדי להדפיס את שמותיהן של כל התלמידות בכיתה:
In [ ]:
names_of_students_in_class = ['Galia', 'Hadas', 'Hen', 'Ilana', 'Ivria', 'Karin', 'Maya', 'Noa']
In [ ]:
student_index = 0
while student_index < len(names_of_students_in_class):
student_name = names_of_students_in_class[student_index]
print(student_name)
student_index = student_index + 1
בעברית, היינו משתמשים בנוסח דומה לזה: עבור כל שם של תלמידה בתוך רשימת שמות התלמידות שבכיתה, הדפס את שם התלמידה.
נכתוב בפייתון, הפעם בעזרת לולאת for
:
In [ ]:
for student_name in names_of_students_in_class:
print(student_name)
השוו את האלגנטיות של הקוד הזה לאלגנטיות של הקוד שמשתמש בלולאת while
, ואת הדמיון בין כל אחת מהן לבין הנוסח המילולי שכתבנו.
לולאת ה־for
שראיתם מתחלקת ל־3 חלקים:
In [ ]:
names_of_students_in_class = ['Galia', 'Hadas', 'Hen', 'Ilana', 'Ivria', 'Karin', 'Maya', 'Noa']
# השם שנמציא
# V Iterable, ערך שאפשר לפרק לכלל איבריו
for student_name in names_of_students_in_class:
print(student_name) # <---- הפעולות לביצוע
נבחן את הלולאה שמדפיסה את שמות התלמידות בכיתה, שלב אחר שלב, כדי להבין איך לולאת for
פועלת.
כמה מקטעי הקוד הבאים לא ירוצו, כיוון שחלק מהקוד מסומן בהערה.
המטרה של קטעי הקוד בחלק הזה של המחברת היא להדגיש איזה קוד רץ באותו רגע.
In [ ]:
names_of_students_in_class = ['Galia', 'Hadas', 'Hen', 'Ilana', 'Ivria', 'Karin', 'Maya', 'Noa']
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
"Galia" | "Hadas" | "Hen" | "Ilana" | "Ivria" | "Karin" | "Maya" | "Noa" |
-8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
בחזרור הראשון, המשתנה החדש שיצרנו, בשם student_name, יצביע על הערך הראשון ברשימה, Galia.
נשים לב שהמשתנה student_name לא היה קיים לפני הלולאה, והלולאה היא המבנה הקסום שיוצר את המשתנה וגורם לו להצביע לערכים:
In [ ]:
for student_name in names_of_students_in_class: # <--- אנחנו פה
# print(student_name)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
"Galia" | "Hadas" | "Hen" | "Ilana" | "Ivria" | "Karin" | "Maya" | "Noa" |
student_name ↑ | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
מייד לאחר מכן יודפס התוכן שאליו student_name מצביע:
In [ ]:
#for student_name in names_of_students_in_class:
print(student_name) # <--- אנחנו פה
סיימנו את האיטרציה! מה עכשיו?
עולים חזרה לראש הלולאה כדי לבדוק אם נשארו עוד איברים לעבור עליהם:
In [ ]:
for student_name in names_of_students_in_class: # <--- אנחנו פה
# print(student_name)
המשתנה student_name יעבור להצביע על האיבר הבא, Hadas:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
"Galia" | "Hadas" | "Hen" | "Ilana" | "Ivria" | "Karin" | "Maya" | "Noa" |
-8 | student_name ↑ | -6 | -5 | -4 | -3 | -2 | -1 |
ושוב, נדפיס את המחרוזת ש־student_name מצביע עליה:
In [ ]:
#for student_name in names_of_students_in_class:
print(student_name) # <---- עכשיו אנחנו פה
כך נמשיך לבצע את הלולאה, עד שנגיע לאיבר האחרון ברשימה, התלמידה Noa:
In [ ]:
for student_name in names_of_students_in_class: # אנחנו פה, אחרי שעברנו על שמות כללל התלמידות, פרט לנועה
# print(student_name)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
"Galia" | "Hadas" | "Hen" | "Ilana" | "Ivria" | "Karin" | "Maya" | "Noa" |
-8 | -7 | -6 | -5 | -4 | -3 | -2 | student_name ↑ |
נדפיס בפעם האחרונה את המחרוזת שעליה אנחנו מצביעים:
In [ ]:
#for student_name in names_of_students_in_class:
print(student_name) # <---- עכשיו אנחנו פה
כשנחזור לראש הלולאה, נראה שעברנו על כל האיברים.
הלולאה תיפסק, וירוץ הקוד שנמצא אחרי הלולאה.
החלק בלולאה | איפה בקוד | דוגמה |
---|---|---|
המבנה שאנחנו רוצים לעבור על כלל איבריו – חייב להיות iterable | אחרי המילה in | names_of_students_in_class |
שם שנמציא למשתנה – בכל חזרור יכיל איבר מתוך ה־iterable | אחרי המילה for, לפני המילה in | student_name |
תוכן הלולאה – הפעולות שנרצה לבצע על כל איבר | אחרי הנקודתיים, בשורה חדשה (אחת או יותר), בהזחה | print(student_name) |
שנו את הקוד שכתבתם למציאת האדם הגבוה ביותר בכיתה, כך שישתמש ב־for
ולא ב־while
.
טיפ: השתמשו בפסאודו־קוד שהוצג לפני כן, והשוו בין ה־for
לבין ה־while
בדוגמה של הדפסת שמות התלמידים.
חשוב!
פתרו לפני שתמשיכו!
לולאת for
יוצרת מבנה אלגנטי וקריא, ומתכנתים רבים מעדיפים אותה על פני לולאת while
.
ננסה לעמוד על ההבדלים בין הלולאות:
נתון להשוואה | לולאת for |
לולאת while |
---|---|---|
מה "מניע" את הלולאה? | iterable שהלולאה תעבור על כל האיברים שלו | ביטוי שערכו הבוליאני שקול ל־True או ל־False |
מתי הלולאה מפסיקה | כשהלולאה עברה על כל האיברים של ה־iterable | כשמגיעים לתנאי של הפונקציה וערכו שקול ל־False |
שימושים עיקריים | ביצוע פעולה עבור כל ערך בסדרת ערכים, כמו איברי רשימה או תווים במחרוזת | חזרה על פעולה כל עוד מצאנו שהמשימה לא הושלמה |
בשלב הנוכחי בקורס, תמיד נוכל להשתמש בלולאת while
במקום בלולאת for
, אך לא תמיד נוכל להחליף לולאות for
בלולאות while
.
באופן כללי, לולאות while
יכולות להוכיח את עצמן כשימושיות מאוד מפעם לפעם.
חשבו, לדוגמה, על מצב שבו אתם צריכים לקבל מהמשתמש קלט חדש כל עוד הקלט שלו לא תקין.
כתבו 3 דוגמאות מילוליות נוספות ללולאות for
.
חשוב!
פתרו לפני שתמשיכו!
בתחרות המרוצים "פורלולה 1", שבה משתתפות בקביעות 6 מכוניות מרוץ, אפשר להמר על הסדר שבו יגיעו המכוניות לקו הסיום.
משתתף זוכה הוא משתתף שהצליח לנחש נכונה את סדר ההגעה של המכוניות לקו הסיום, עם לא יותר מ־2 טעויות.
כתבו פונקציה שמקבלת הימור בודד ואת סדר ההגעה של המכוניות במרוץ, והחזירו אם ההימור זכה או הפסיד.
לדוגמה, במרוץ האחרון סדר ההגעה לקו הסיום היה:
[1, 2, 3, 4, 5, 6]
הנה דוגמאות להימורים של משתתפים ולתוצאתם:
הרשימה הבאה מכילה tuple־ים בגודל 2 איברים:
In [ ]:
words = [('star', 'rats'), ('wolf', 'flow'), ('racecar', 'racecar'), ('ekans', 'snake')]
כתבו לולאת for
שתדפיס עבור כל זוג מחרוזות ברשימה: Flip "X" to get "Y".
לדוגמה, עבור הזוג האחרון מתוך 4 הזוגות, היא תדפיס: Flip "ekans" to get "snake".
חשוב!
פתרו לפני שתמשיכו!
כדי לפתור את התרגיל, כתבתם קוד שהשתמש במיקום של הערך הראשון (0) ושל הערך השני (1).
כך אני פתרתי אותו:
In [ ]:
for word_and_reversed_word in words:
word = word_and_reversed_word[0]
reversed_word = word_and_reversed_word[1]
print(f'Flip "{word}" to get "{reversed_word}".')
נבחן דרך נוספת לפתור את התרגיל, רק שהפעם נשתמש בטריק שנקרא unpacking (או "פירוק").
כיוון שכל tuple ברשימת words מכיל בדיוק 2 איברים, נוכל לתת להם שמות כבר בראש הלולאה ולחלץ אותם מה־tuple:
In [ ]:
for word, reversed_word in words:
print(f'Flip "{word}" to get "{reversed_word}".')
בכל חזרור של הלולאה שכתובה למעלה, ה־for
יעבור על tuple בודד מתוך words, בדיוק כמו שקרה עד עכשיו.
השינוי הוא שבמקום שה־tuple יישמר כמו שהוא במשתנה בודד שהוגדר בראש הלולאה, הערכים שבו "יחולצו" למשתנים בראש הלולאה.
הטריק הזה עובד כיוון שבראש הלולאה כתבנו מספר משתנים שזהה למספר הערכים שנמצאים בכל tuple.
לפניכם תרשים המתאר את תוכן המשתנה words.
הטבלה הגדולה מייצגת את הרשימה words, וכל אחד מהתאים שבה מייצג tuple בתוך הרשימה.
בכל חזרור של ה־for
המופיע למעלה, המשתנה word יקבל ערך שמסומן באדום, והמשתנה reversed_word יקבל את הערך הירוק התואם לו.
0 | 1 | 2 | 3 | ||||||||||||||||||||||||
|
|
|
|
||||||||||||||||||||||||
-4 | -3 | -2 | -1 |
בדרך כלל נעדיף להימנע משינוי רשימה בזמן ביצוע לולאת for
.
שינויים כאלו עלולים לגרום להתנהגות בלתי צפויה, ללולאות אין־סופיות ולקוד קשה במיוחד לקריאה.
נבחן את קטע הקוד הבא, שעבור כל איבר ברשימה, מוציא איבר מסוף הרשימה:
In [ ]:
numbers = ['a', 'b', 'c', 'd', 'e']
print(f"The reader expects {len(numbers)} iterations.")
for i in numbers:
j = numbers.pop()
print(i, j)
הלולאה הסתיימה מוקדם מהרגיל, כיוון שכשניסתה להגיע לתא שערכו 'd'
הוא כבר לא היה שם.
קוד שכזה אינו צפוי, קשה לקריאה ויוצר תקלים. מומלץ מאוד לא לשנות את מספר האיברים ב־iterable בזמן הריצה.
פתרון אפשרי הוא ליצור עותק של הרשימה באמצעות הפעולה list.copy()
ולהשתמש בו במקום:
In [ ]:
numbers = ['a', 'b', 'c', 'd', 'e']
numbers_to_pop = numbers.copy()
print(f"The reader expects {len(numbers)} iterations.")
for i in numbers:
j = numbers_to_pop.pop()
print(i, j)
ננסה להכפיל כל תא ברשימה שלנו פי 2:
In [ ]:
numbers = [1, 3, 5]
print(f'This code will multiply every item in {numbers} by 2.')
print(f'The user expects:')
print(f'[{numbers[0] * 2}, {numbers[1] * 2}, {numbers[2] * 2}]')
for num in numbers:
num = num * 2
print("The final result:")
print(numbers)
נוכל לראות שהרשימה נותרה ללא שינוי, למרות הלולאה שתכליתה היה להכפיל את איברי הרשימה פי 2.
כדי להבין למה זה קרה, ננסה להיזכר בשיעור על mutability.
במהלך כל חזרור, המשתנה num מקבל ערך כלשהו להצביע עליו.
לדוגמה, בחזרור הראשון num מצביע על numbers[0]
, המקום הראשון ברשימה:
בעצם הפעולה num * 2
אנחנו יוצרים ערך חדש שמאוחסן בכתובת שונה.
הפעולה num = num * 2
לא באמת "תשנה ערך בתוך num", אלא תגרום לו להצביע על ערך אחר.
נסכם כך: המשתנה num הצביע תחילה על מספר שנמצא בתוך הרשימה, ועכשיו הוא מצביע על מספר אחר.
ההשמה, כרגיל, שינתה את המקום שעליו המשתנה מצביע, ולא את הערך שהמשתנה מצביע עליו.
בקוד שמתואר למעלה, לא שינינו את האיבר בתוך הרשימה.
In [ ]:
def total(numbers):
total = 0
for number in numbers:
total = total + number
return total
print(total([1, 2, 3]))
In [15]:
def acronym(sentence):
acronym_word = ''
for word in sentence.split():
if len(word) >= 1:
acronym_word = acronym_word + word[0]
return acronym_word
print(acronym(''))
קלוט מהמשתמש מספרים. אם צריך, המר את הקלט כך שיהיה מסוג רשימה, ובה יהיו מספרים שלמים. סכום את האיברים הגדולים מ־0.
In [ ]:
def to_numbers(strings):
numbers = []
for semi_number in strings:
if semi_number.isdecimal():
numbers.append(int(semi_number))
return numbers
def sum_positives(numbers):
total = 0
for number in numbers:
if number > 0:
total = total + number
return total
user_numbers = input("Enter numbers seperated by comma: ")
stringy_numbers = user_numbers.replace(' ', '').split(',')
numbers = to_numbers(stringy_numbers)
print(sum_positives(numbers))
סכום את האיברים שמופיעים בכל מקום שביעי ברשימה.
In [ ]:
def sum_only_7th_places(numbers):
total = 0
for i in numbers[6::7]:
total = total + i
return total
print(sum_only_7th_places([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]))
אקרוסטיכון הוא אמצעי ספרותי שבו משתמשים בכתיבת שירה.
בשיר שבו יש אקרוסטיכון, כשנחבר את האות הראשונה בכל השורות, נקבל מסר מסוים.
ניקח לדוגמה את שירו של אדגר אלן פו, "אקרוסטיכון", שאותו הוא הקדיש ללטישיה אליזבת' לאנדון ובו מופיע האקרוסטיכון ELIZABETH:
Elizabeth it is in vain you say
Love not — thou sayest it in so sweet a way:
In vain those words from thee or L.E.L.
Zantippe's talents had enforced so well:
Ah! if that language from thy heart arise,
Breath it less gently forth — and veil thine eyes.
Endymion, recollect, when Luna tried
To cure his love — was cured of all beside —
His follie — pride — and passion — for he died.
כתבו פונקציה שמקבלת שיר כמחרוזת.
החזירו את האקרוסטיכון שנוצר אם ניקח את האות הראשונה מכל שורה בשיר.
שנים רבות חלפו מאז אותם ימי הקיץ בשדות השעורה, והזמרת סיגנט שכחה היכן החביאה את הזהב שלה.
בידי סיגנט יש מפה, לפיה היא נמצאת כרגע בנקודה (0, 0) בשדה החיטה.
המפה מתארת אילו צעדים היא צריכה לעשות כדי להגיע למטמון.
עזרו לסיגנט לחשב: בהינתן שהיא תלך לפי כל הצעדים שמופיעים במפה – באיזו נקודה יימצא המטמון?
לדוגמה: עבור הצעדים [(1, 5), (6, -2), (4, 3)]
יוחזר שהמטמון נמצא בנקודה (11, 6)
.
הסבר מורחב: קבלו רשימה של צעדים המורים לסיגנט כיצד ללכת.
כל "צעד" מורכב מזוג מספרים שלמים, שיכולים להיות שליליים – הראשון מסמל כמה צעדים ללכת ימינה, והשני מסמל כמה צעדים ללכת למעלה.
אם המספר הראשון שלילי, עליה ללכת את מספר הצעדים הזה שמאלה. אם המספר השני שלילי, עליה ללכת את מספר הצעדים הזה למטה.
כתבו פונקציה שמקבלת רשימה של צעדים ומחזירה את מיקום המטמון.
בגבעת ווטרשיפ קצב ההתרבות גבוה. בכל שנה נוספים עוד ועוד ארנבים לארנבייה.
חומש הארנב החליט לנהל מעקב דמוגרפי אחרי הגידול.
הוא מעוניין שתבנו לו פונקציה שמקבלת כפרמטר רשימה של מספר הארנבים שנולדו בכל שנה.
הפונקציה תחזיר רשימה שבה כל תא מייצג את הכמות הנצברת של הארנבים בארנבייה עד כה.
לדוגמה: עבור הרשימה [1, 2, 3, 4]
, הפונקציה תחזיר [1, 3, 6, 10]
.