יסודות התכנות בשפת Python · פרק 6 — מבני נתונים⌂📚🏠
פרק 6

מבני נתונים

30 שעות לימוד

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

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

6.1 היכרות עם המושג מבנה נתונים

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

דוגמה 6.1
יש לנו ארבע קריאות של מתח סוללה. נדפיס את הקריאות שגבוהות מן הממוצע שלהן, כשיש לנו רק משתנים נפרדים.
פתרון:
r1 = 12.1
r2 = 11.6
r3 = 12.4
r4 = 11.9
average = (r1 + r2 + r3 + r4) / 4
if r1 > average:
    print(r1)
if r2 > average:
    print(r2)
if r3 > average:
    print(r3)
if r4 > average:
    print(r4)
פלט
12.1
12.4

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

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

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

readings = [12.1, 11.6, 12.4, 11.9]
print(readings)
print(readings[0])
print(len(readings))
print(round(sum(readings) / len(readings), 2))
פלט
[12.1, 11.6, 12.4, 11.9]
12.1
4
12.0

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

המבנההכתיב שלוהאם הוא משתנה אחרי שנוצר?דוגמה לשימוש
רשימה list[12.1, 11.6]כן: מוסיפים, מוחקים ומשניםיומן קריאות שגדל עם הזמן
טאפל tuple(8, 12.4)לא: נשאר כפי שיצרנו אותוקריאה אחת: השעה והמתח שלה יחד
למורה: בתכנית הלימודים של כיתה י׳ החומר מסתיים באמצע הפרק הזה, וכיתה י״א ממשיכה משם.
שאלה 6.1: קוראים מדידות בלולאה ושומרים רק את הקריאה האחרונה במשתנה אחד. על אילו מן השאלות הבאות אפשר לענות בלי רשימה?
(א) מה סכום הקריאות?
(ב) כמה קריאות גבוהות מן הממוצע?
(ג) מהי הקריאה הגבוהה ביותר?

6.2 אתחול רשימה ופעולות על איבריה (list)

מאתחלים רשימה בכתיבת איבריה בין סוגריים מרובעים. האיברים יכולים להיות מספרים או מחרוזות, ואפשר גם לערבב ביניהם, אבל רשימה שכל איבריה מאותו טיפוס נוחה יותר לשימוש. הטיפוס של רשימה הוא list, והפעולה len נותנת את מספר איבריה.

volts = [12.1, 11.6, 12.4]
parts = ["R1", "C4", "D2", "LED"]
mixed = ["R1", 470, 0.25]
print(volts)
print(parts)
print(mixed)
print(type(volts), len(parts))
פלט
[12.1, 11.6, 12.4]
['R1', 'C4', 'D2', 'LED']
['R1', 470, 0.25]
<class 'list'> 4

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

log = []
print(log, len(log))
print(list())
print(list("ADC"))
print(list(range(4)))
print("BAT=12.42;TMP=31.5".split(";"))
פלט
[] 0
[]
['A', 'D', 'C']
[0, 1, 2, 3]
['BAT=12.42', 'TMP=31.5']
טעות נפוצה: מספר אינו אוסף ערכים, ולכן list(5) עוצר את התוכנית. לרשימה שיש בה רק את המספר כותבים [5]. ולא קוראים למשתנה list, כי כך מאבדים את הפעולה המוכנה שנושאת את השם הזה.

הפעולה print מדפיסה את כל הרשימה עם הסוגריים והפסיקים. כדי להגיע לאיבר כותבים את האינדקס שלו בסוגריים מרובעים, כמו במחרוזות. והאיבר מתנהג כמו משתנה רגיל: מציבים בו ערך חדש ומחשבים איתו. הפעולות sum, max ו-min מפרק 2 עובדות על הרשימה כולה:

loads = [60, 100, 40]
print(loads)
print(loads[0], "W and", loads[1], "W")
loads[1] = 75
loads[2] += 20
loads[0] = loads[0] * 2
print(loads)
print(sum(loads), max(loads), min(loads))
פלט
[60, 100, 40]
60 W and 100 W
[120, 75, 60]
255 120 60

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

רשימת מונים:

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

דוגמה 6.2
בקוד הצבעים של נגדים יש לכל ספרה מ-0 עד 9 צבע: שחור 0, חום 1, אדום 2, וכן הלאה. נקרא את הספרות של הפסים שהופיעו על נגדים במחסן עד הערך −1, ונמנה כמה פעמים הופיעה כל ספרה.
פתרון:
counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
digit = int(input("Digit (-1 to stop): "))
while digit != -1:
    counts[digit] += 1
    digit = int(input("Digit (-1 to stop): "))
print(counts)
print("Red bands:", counts[2])
פלט
Digit (-1 to stop): 2
Digit (-1 to stop): 7
Digit (-1 to stop): 2
Digit (-1 to stop): 1
Digit (-1 to stop): 0
Digit (-1 to stop): 2
Digit (-1 to stop): -1
[1, 1, 3, 0, 0, 0, 0, 1, 0, 0]
Red bands: 3
counts01112330405060718090
רשימת המונים אחרי הריצה: האינדקס הוא הספרה, לכל ספרה הצבע שלה, והתא הצבוע הוא מונה שגדל

הפקודה counts[digit] += 1 מגדילה ישירות את המונה המתאים. בלי רשימה היינו צריכים עשרה משתנים ועשרה תנאים.

שימו לב: הקלט הופך כאן לאינדקס, ולכן מספר מחוץ לתחום, כמו 12, עוצר את התוכנית. וכשהערכים מתחילים מ-1 ולא מ-0 מחסרים 1: המונה של לחצן מספר 1 נמצא בתא 0. זה בדיוק מה שנצטרך בשאלת הבגרות בסוף הפרק.
שאלה 6.2: בלוח בקרה יש ארבעה לחצנים שממוספרים מ-1 עד 4. מה מדפיסה התוכנית עבור הקלטים 1, 3, 3, 4, 1, 3, 0?
presses = [0, 0, 0, 0]
button = int(input("Button: "))
while button != 0:
    presses[button - 1] += 1
    button = int(input("Button: "))
print(presses)

רשימת צוברים:

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

דוגמה 6.3
בסדנה עובדים שלושה טכנאים, ומספריהם 0, 1 ו-2. נקרא דיווחי עבודה עד הערך −1: מספר הטכנאי ומספר השעות. נדפיס את סך השעות של כל טכנאי ואת סך כל השעות.
פתרון:
hours = [0, 0, 0]
tech = int(input("Technician (-1 to stop): "))
while tech != -1:
    hours[tech] += float(input("Hours: "))
    tech = int(input("Technician (-1 to stop): "))
print(hours)
print("Total:", sum(hours))
פלט
Technician (-1 to stop): 0
Hours: 6.5
Technician (-1 to stop): 2
Hours: 8
Technician (-1 to stop): 0
Hours: 3
Technician (-1 to stop): -1
[9.5, 0, 8.0]
Total: 17.5

טכנאי מספר 0 הופיע פעמיים, ושעותיו נצברו בתא שלו.

6.3 המבנה הסידורי של רשימה (List), גישה לאיבר באמצעות אינדקס

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

samples0.20-51.41-43.12-34.83-22.64-1
לכל איבר שני אינדקסים: חיובי מעל התא ושלילי מתחתיו
samples = [0.2, 1.4, 3.1, 4.8, 2.6]
print(samples[0], samples[2], samples[4])
print(samples[-1], samples[-5])
samples[-1] = 0.0
print(samples)
פלט
0.2 3.1 2.6
2.6 0.2
[0.2, 1.4, 3.1, 4.8, 0.0]

האינדקס השלילי פוטר אותנו מלכתוב samples[len(samples) - 1], עובד בכל אורך של רשימה, ומתאים גם לשינוי.

טעות נפוצה: האינדקס הגבוה ביותר ברשימה של חמישה איברים הוא 4, ולא 5. בקשה של איבר שאינו קיים עוצרת את התוכנית:
samples = [0.2, 1.4, 3.1, 4.8, 2.6]
print(samples[5])
פלט
IndexError: list index out of range
דוגמה 6.4
האינדקס יכול להיות משתנה, וכאן הכוח של הרשימה: במשתנה שגדל בכל סבב עוברים על כל האיברים. נמנה בלולאת while מפרק 4 את הדגימות שגבוהות מ-2.5 V.
פתרון:
samples = [0.2, 1.4, 3.1, 4.8, 2.6]
high = 0
i = 0
while i < len(samples):
    if samples[i] > 2.5:
        high += 1
    i += 1
print("Above 2.5 V:", high)
פלט
Above 2.5 V: 3

התנאי הוא i < len(samples). אילו כתבנו <= היינו מוסיפים סבב אחרי האיבר האחרון, והתוכנית הייתה נעצרת באותה שגיאה.

תרגול מהיר — אינדקס, לרשימה v = [5, 10, 15, 20, 25, 30]
1v[0]
2v[3]
3v[-1]
4v[-2]
5v[len(v) - 2]
6v[1] + v[-1]
תשובות: 1 5 2 20 3 30 4 25 5 25 6 40

6.4 חיתוך רשימות (slicing)

חיתוך נותן לנו חלק מן הרשימה כרשימה חדשה. כותבים s[start:stop:step]: מתחילים מן האינדקס start, עוצרים לפני האינדקס stop, ומתקדמים בצעד step. אלה אותם כללים שהכרנו במחרוזות:

s100-6201-5302-4403-3504-2605-1s[1:4]
האינדקס 4 הוא גבול העצירה ואינו נכנס לתוצאה, ולכן מתקבלים שלושה איברים
s = [10, 20, 30, 40, 50, 60]
print(s[1:4])
print(s[:3], s[3:])
print(s[-2:])
print(s[::2])
print(s[::-1])
print(s[2:100])
print(s[4:2])
print(s)
פלט
[20, 30, 40]
[10, 20, 30] [40, 50, 60]
[50, 60]
[10, 30, 50]
[60, 50, 40, 30, 20, 10]
[30, 40, 50, 60]
[]
[10, 20, 30, 40, 50, 60]
דוגמה 6.5
מכשיר מדידה צריך כמה רגעים כדי להתייצב, ולכן אי אפשר לסמוך על שתי הדגימות הראשונות. נשמיט אותן ונחשב את הממוצע של השאר. אחר כך נוריד את קצב הדגימה בחצי, בלקיחת דגימה אחת מכל שתיים.
פתרון:
samples = [0.1, 0.9, 2.4, 2.5, 2.6, 2.4, 2.5]
stable = samples[2:]
print(stable)
print(round(sum(stable) / len(stable), 2))
print(stable[::2])
פלט
[2.4, 2.5, 2.6, 2.4, 2.5]
2.48
[2.4, 2.6, 2.5]

השמה אינה מעתיקה רשימה:

טעות נפוצה: אנחנו חושבים ש-b = a יוצר העתק, משנים את b ומצפים ש-a תישאר כפי שהייתה. אבל הפקודה הזאת לא יוצרת רשימה שנייה, אלא נותנת לאותה רשימה שם נוסף, וכל שינוי דרך אחד השמות נראה גם בשני:
a = [12, 9, 5]
b = a
b[0] = 99
print(a)
print(b)
פלט
[99, 9, 5]
[99, 9, 5]

כשרוצים העתק אמיתי משתמשים בחיתוך a[:], או בפעולה list(a), ונוצרת רשימה חדשה בזיכרון:

a = [12, 9, 5]
b = a[:]
b[0] = 99
print(a)
print(b)
פלט
[12, 9, 5]
[99, 9, 5]
b = aab[99, 9, 5]b = a[:]ab[12, 9, 5][99, 9, 5]
משמאל: שני שמות ורשימה אחת. מימין: לכל שם רשימה משלו. שני הציורים הם אחרי הפקודה b[0] = 99
תרגול מהיר — חיתוך, לרשימה v = [1, 2, 3, 4, 5, 6]
1v[2:4]
2v[:2]
3v[4:]
4v[-3:-1]
5v[1::2]
6v[::-2]
תשובות: 1 [3, 4] 2 [1, 2] 3 [5, 6] 4 [4, 5] 5 [2, 4, 6] 6 [6, 4, 2]
שאלה 6.3: מה מדפיסה התוכנית הבאה?
a = [1, 2, 3]
b = a
c = a[:]
a[0] = 7
b[1] = 8
c[2] = 9
print(a)
print(b)
print(c)

6.5 הוספת איברים לרשימה

הרשימה גדלה תוך כדי ריצת התוכנית. הפעולה append מוסיפה איבר אחד לסוף, וזו הדרך שמשתמשים בה רוב הזמן. האופרטור + משרשר שתי רשימות ויוצר רשימה חדשה, ו-+= מוסיף רשימה לסוף אותה רשימה. והאופרטור * משכפל את הרשימה, וזו הדרך הקצרה ביותר לאתחל רשימת מונים: במקום עשרת האפסים שבסעיף 6.2 כותבים [0] * 10.

log = [12.1]
log.append(11.8)
print(log)
log = log + [11.9, 12.0]
print(log)
log += [12.2]
print(log)
print([0] * 5)
פלט
[12.1, 11.8]
[12.1, 11.8, 11.9, 12.0]
[12.1, 11.8, 11.9, 12.0, 12.2]
[0, 0, 0, 0, 0]
דוגמה 6.6
נקרא הספקים של עומסים חשמליים עד הערך 0, ואיננו יודעים מראש כמה עומסים המשתמש יקליד. נתחיל ברשימה ריקה ונוסיף לה בכל סבב.
פתרון:
loads = []
watts = float(input("Load W (0 to stop): "))
while watts != 0:
    loads.append(watts)
    watts = float(input("Load W (0 to stop): "))
print(loads)
print("Count:", len(loads))
print("Total:", sum(loads))
פלט
Load W (0 to stop): 60
Load W (0 to stop): 100
Load W (0 to stop): 40.5
Load W (0 to stop): 0
[60.0, 100.0, 40.5]
Count: 3
Total: 200.5

הדפוס הזה חוזר הרבה: רשימה ריקה לפני הלולאה, ו-append בתוכה. הוא פותר את הבעיה מתחילת הפרק, כי מספר הקריאות כבר לא נכתב מראש בתוכנית.

טעות נפוצה: האופרטור + משרשר רשימה רק עם רשימה, ולכן מקיפים את האיבר החדש בסוגריים מרובעים: log + 11.8 עוצר את התוכנית, ו-log + [11.8] נכון. וגם לא יוצרים איבר חדש בהצבה לאינדקס שאינו קיים: ברשימה ריקה log[0] = 12.1 עוצר את התוכנית, והוספה נעשית בפעולה append.
שאלה 6.4: מה מדפיסה התוכנית הבאה?
a = [1]
a.append(2)
b = a + [3]
a += [4, 5]
print(a)
print(b)
print([7] * 2 + [8])

6.6 שימוש ב-range לאתחול רשימה

הפעולה range מייצרת סדרת מספרים שלמים. ממירים אותה לרשימה בפעולה list, ומקבלים רשימה ארוכה בלי לכתוב את איבריה. יש לה שלוש צורות:

פקודהמה היא עושה
range(stop)מ-0 עד לפני stop
range(start, stop)מ-start עד לפני stop
range(start, stop, step)מ-start עד לפני stop, בצעד step, שיכול להיות שלילי
print(list(range(5)))
print(list(range(2, 8)))
print(list(range(0, 50, 10)))
print(list(range(5, 0, -1)))
print(range(5))
פלט
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6, 7]
[0, 10, 20, 30, 40]
[5, 4, 3, 2, 1]
range(0, 5)

הגבול stop לעולם אינו נכנס לסדרה, כמו בחיתוך. והשורה האחרונה מראה ש-range לבדה אינה רשימה, אלא תיאור של סדרת מספרים שנוצרת לפי הצורך, ולכן כתבנו list כדי לראות אותה.

דוגמה 6.7
בודקים את התגובה של מגבר בתדרים מ-100 Hz עד 1000 Hz, בצעד של 100 Hz. נבנה את רשימת התדרים, ואז רשימה של מספרי שמונה ערוצים של ממיר תקבילי-ספרתי, החל מ-1.
פתרון:
freqs = list(range(100, 1100, 100))
print(freqs)
channels = list(range(1, 9))
print(channels)
פלט
[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]
[1, 2, 3, 4, 5, 6, 7, 8]

הגבול העליון בשורה הראשונה הוא 1100 כדי ש-1000 ייכנס לרשימה.

טעות נפוצה: הפעולה range מקבלת רק מספרים שלמים, ולכן range(0, 1, 0.1) עוצר את התוכנית: אין צעד עשרוני.
תרגול מהיר — הפעולה range
1list(range(4))
2list(range(1, 4))
3list(range(0, 9, 3))
4list(range(10, 4, -2))
תשובות: 1 [0, 1, 2, 3] 2 [1, 2, 3] 3 [0, 3, 6] 4 [10, 8, 6]

6.7 לולאת for העוברת על איברי רשימה (for)

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

for item in sequence:
    statement
    statement
next_statement
נשאר איבר?item = האיבר הבאגוף הלולאההפקודה הבאהTrueFalse
תרשים זרימה של לולאת for: היא חוזרת כל עוד יש ברשימה איבר שלא עברנו עליו
volts = [12.1, 11.6, 12.4]
for v in volts:
    print("Reading:", v)
print("Done")
פלט
Reading: 12.1
Reading: 11.6
Reading: 12.4
Done
מעקב אחר התוכנית: בכל סבב v מחזיק את האיבר הבא
פקודהvoltsvפלט
volts = [12.1, 11.6, 12.4][12.1, 11.6, 12.4]
for v in volts:12.1
print("Reading:", v)Reading: 12.1
for v in volts:11.6
print("Reading:", v)Reading: 11.6
for v in volts:12.4
print("Reading:", v)Reading: 12.4
for v in volts:
print("Done")Done

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

דוגמה 6.8
נחזור לשאלה שפתחה את הפרק: נדפיס את הקריאות שגבוהות מן הממוצע שלהן. השוו בין התוכנית הזאת לתוכנית עם ארבע פקודות התנאי בסעיף 6.1.
פתרון:
readings = [12.1, 11.6, 12.4, 11.9]
average = sum(readings) / len(readings)
for r in readings:
    if r > average:
        print(r)
פלט
12.1
12.4

התוכנית נשארת כפי שהיא עבור ארבע קריאות ועבור ארבעת אלפים, ורק השורה הראשונה משתנה.

טעות נפוצה: משתנה הלולאה מקבל את ערך האיבר, והשמה חדשה אליו אינה משנה את הרשימה. אחרי סיום הלולאה נשאר בו הערך האחרון. כדי לשנות איברים צריך את האינדקסים שלהם, וזה הנושא של הסעיף הבא:
volts = [12, 9, 5]
for v in volts:
    v = v * 2
print(volts)
print(v)
פלט
[12, 9, 5]
10

לולאת for עובדת גם על כל מה שאפשר לעבור על חלקיו, למשל מחרוזת: for ch in "ADC":. ועל רשימה ריקה הגוף שלה אינו מתבצע אף פעם, ואין שום שגיאה.

שאלה 6.5: מה מדפיסה התוכנית הבאה? בנו טבלת מעקב ובדקו.
items = [4, 7, 2, 9]
s = 0
for x in items:
    if x % 2 == 1:
        s += x
    print(s)

6.8 שילוב של לולאת for עם range

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

for i in range(4):
    print(i, i * 5)
פלט
0 0
1 5
2 10
3 15
currents = []
for k in range(3):
    currents.append(float(input("Current: ")))
print(currents)
print(max(currents))
פלט
Current: 1.5
Current: 0.75
Current: 2.25
[1.5, 0.75, 2.25]
2.25

השימוש החשוב ביותר הוא מעבר על האינדקסים של הרשימה: for i in range(len(v)): נותן 0, אחר כך 1 וכן הלאה עד האינדקס האחרון, בכל אורך של רשימה. זה נחוץ בשלושה מצבים, ולולאה על האיברים אינה מספיקה באף אחד מהם.

דוגמה 6.9
מצב ראשון: משנים איברים. יש לנו קריאות של ממיר בן עשר סיביות, ונרצה להמיר כל אחת מהן למתח באותה רשימה.
פתרון:
adc = [0, 256, 512, 1023]
for i in range(len(adc)):
    adc[i] = round(adc[i] * 5 / 1023, 2)
print(adc)
פלט
[0.0, 1.25, 2.5, 5.0]

בסעיף הקודם ראינו ש-v = v * 2 אינו משנה את הרשימה. לעומת זאת adc[i] = ... כותב בתא של הרשימה עצמה.

דוגמה 6.10
מצב שני: שתי רשימות מקבילות. ברשימה אחת המתחים של שלושה קווי הזנה, וברשימה שנייה הזרמים שלהם, באותו סדר. אותו אינדקס מציין את אותו קו בשתי הרשימות. נבנה את רשימת ההספקים.
פתרון:
volts = [12, 5, 3.3]
amps = [0.5, 0.2, 0.1]
power = []
for i in range(len(volts)):
    power.append(round(volts[i] * amps[i], 2))
print(power)
print("Total:", round(sum(power), 2))
פלט
[6.0, 1.0, 0.33]
Total: 7.33
דוגמה 6.11
מצב שלישי: איבר ושכנו. אות ספרתי מדוד קופץ בין כ-0 V לכ-3.3 V. נחפש את הקצוות, כלומר את המקומות שבהם דגימה משתנה ביותר מ-1 V לעומת קודמתה.
פתרון:
samples = [0.1, 0.2, 3.2, 3.3, 3.2, 0.2, 0.1]
for i in range(1, len(samples)):
    step = samples[i] - samples[i - 1]
    if abs(step) > 1:
        print("Edge at index", i)
פלט
Edge at index 2
Edge at index 5

התחלנו מ-range(1, ...) כי לאיבר הראשון אין שכן לפניו. אילו התחלנו מ-0 היינו משווים את הדגימה הראשונה לאיבר [-1], כלומר לדגימה האחרונה, וזו שגיאה לוגית שאינה נותנת שום הודעה.

הפקודות break ו-continue מפרק 4 עובדות גם ב-for: כשמחפשים את האיבר הראשון שמקיים תנאי, כותבים break מיד כשמוצאים אותו, ולא ממשיכים במעבר.

טעות נפוצה: יש שתי טעויות חוזרות בשורה הראשונה של הלולאה. הראשונה range(len(v) + 1): סבב עודף מביא את האינדקס אל מחוץ לרשימה. השנייה range(v): שכחנו len, ו-range מקבלת רשימה במקום מספר. שתיהן עוצרות את התוכנית.
מה שרוציםהלולאה המתאימה
רק לקרוא את האיברים, אחד אחרי השניfor v in values:
לשנות איברים, או לעבוד עם שתי רשימות יחד, או עם איבר ושכנוfor i in range(len(values)):
לחזור מספר ידוע של פעמיםfor k in range(n):
לא יודעים כמה פעמים נחזור: עד זקיף או עד שתנאי מתקייםwhile condition:
שאלה 6.6: מה מדפיסה התוכנית הבאה?
a = [2, 5, 1, 8]
t = 0
for i in range(1, len(a)):
    if a[i] > a[i - 1]:
        t += 1
print(t)
for i in range(0, len(a), 2):
    print(a[i])

6.9 אופרטור היחס in (in)

הביטוי x in values שואל: האם הערך הזה הוא איבר ברשימה? התוצאה היא ערך בוליאני, וההפך שלו הוא x not in values. הכרנו את האופרטור הזה במחרוזות, וכאן הוא משווה איבר שלם. בדוגמה הבאה הרשימה היא סדרת E12, כלומר הערכים התקניים של נגדים. המספר 47 והמחרוזת "47" אינם אותו ערך, ולכן בשורה האחרונה יצא False.

e12 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82]
print(47 in e12)
print(50 in e12)
print(50 not in e12)
print("47" in e12)
פלט
True
False
True
False
הערה: למילה in יש שני תפקידים: ב-for v in values: היא חלק מן הלולאה, וב-if v in values: היא שאלה שתשובתה True או False.
דוגמה 6.12
ברשימת הרכיבים שנעשה בהם שימוש בלוח, חלקם חוזרים. נבנה רשימה של הרכיבים השונים, בלי כפילויות.
פתרון:
codes = ["R1", "C4", "R1", "D2", "C4"]
unique = []
for code in codes:
    if code not in unique:
        unique.append(code)
print(unique)
פלט
['R1', 'C4', 'D2']

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

שימו לב: במחרוזת in מחפש חלק ממנה, וברשימה הוא מחפש איבר ששווה בדיוק: "V" in ["12V"] נותן False. הוא משווה בשוויון מדויק, ולכן אינו מתאים למספרים עשרוניים מחושבים, כפי שלמדנו בסעיף 3.3: 0.3 in [0.1 + 0.2] נותן False.
שאלה 6.7: מה מדפיסה התוכנית הבאה?
a = [1, 3, 5]
b = [3, 4, 5, 6]
for x in b:
    if x in a:
        print(x)
print(2 in a or 4 in b)
print([3] in a)

6.10 עבודה עם פעולות של המחלקה list

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

פקודהמה היא עושה
a.append(x)מוסיפה את האיבר x לסוף
a.insert(i, x)מכניסה את האיבר x באינדקס i, ומזיזה את מה שאחריו
a.extend(b)מוסיפה לסוף את כל איברי b
a.remove(x)מוחקת את האיבר הראשון ששווה ל-x
a.count(x)נותנת כמה פעמים x מופיע, ואינה משנה את הרשימה
a.sort()ממיינת את הרשימה בסדר עולה במקומה
a.clear()מרוקנת את הרשימה
parts = ["R1", "C4", "D2", "C4"]
parts.append("LED")
print(parts)
parts.remove("C4")
print(parts)
print(parts.count("C4"))
parts.insert(1, "Q1")
print(parts)
parts.extend(["U1", "U2"])
print(parts)
parts.clear()
print(parts)
פלט
['R1', 'C4', 'D2', 'C4', 'LED']
['R1', 'D2', 'C4', 'LED']
1
['R1', 'Q1', 'D2', 'C4', 'LED']
['R1', 'Q1', 'D2', 'C4', 'LED', 'U1', 'U2']
[]

הפעולה remove מחקה רק את ההופעה הראשונה של הרכיב C4, ולכן נשאר אחד, וזה מה ש-count אישרה. הפעולה append מוסיפה איבר אחד גם אם הוא רשימה, ואילו extend מוסיפה את איברי הרשימה אחד אחד.

הפעולה sort:

הפעולה sort ממיינת מספרים לפי ערכם, ומחרוזות לפי סדר מילוני, כלומר תו אחרי תו כמו במילון. ואם כותבים reverse=True המיון הוא בסדר יורד:

r = [470, 10, 220, 1000, 47]
r.sort()
print(r)
r.sort(reverse=True)
print(r)
names = ["diode", "LED", "resistor", "Capacitor"]
names.sort()
print(names)
codes = ["100", "47", "9"]
codes.sort()
print(codes)
פלט
[10, 47, 220, 470, 1000]
[1000, 470, 220, 47, 10]
['Capacitor', 'LED', 'diode', 'resistor']
['100', '47', '9']

ההשוואה בין תווים נעשית לפי המספר של כל תו בטבלת הקידוד, שאותו נותנת ord מפרק 2. בטבלה הזאת כל האותיות הגדולות באות לפני כל האותיות הקטנות, ולכן LED הגיעה לפני diode. גם ספרות במחרוזת הן תווים, ולכן המחרוזת "100" הגיעה לפני המחרוזת "9", אף שהמספר 100 גדול מ-9.

טעות נפוצה: הפעולה sort ממיינת במקום ואינה נותנת ערך: מה שהיא מחזירה הוא None, כלומר "כלום". אם נכתוב r = r.sort() נאבד את הרשימה, וכך גם עם append. כותבים את הפקודה לבדה בשורה:
r = [470, 10, 220]
r = r.sort()
print(r)
print(r[0])
פלט
None
TypeError: 'NoneType' object is not subscriptable
דוגמה 6.13
חמש דגימות של מתח קבוע, ואחת מהן השתבשה בגלל רעש. הממוצע מתרחק מן האמת, ואילו החציון, שהוא האיבר האמצעי אחרי המיון, אינו מושפע. נחשב את שניהם, ונמיין העתק כדי לשמור על הסדר המקורי של הדגימות.
פתרון:
samples = [2.5, 2.6, 9.9, 2.4, 2.5]
ordered = samples[:]
ordered.sort()
print(ordered)
print("Median:", ordered[len(ordered) // 2])
print("Average:", round(sum(samples) / len(samples), 2))
פלט
[2.4, 2.5, 2.5, 2.6, 9.9]
Median: 2.5
Average: 3.98

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

הפעולה remove:

טעות נפוצה: מחיקת איבר שאינו קיים עוצרת את התוכנית:
parts = ["R1", "C4"]
parts.remove("D2")
פלט
ValueError: list.remove(x): x not in list

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

parts = ["R1", "C4", "R1", "D2", "R1"]
name = "Q7"
if name in parts:
    parts.remove(name)
else:
    print(name, "not found")
while "R1" in parts:
    parts.remove("R1")
print(parts)
פלט
Q7 not found
['C4', 'D2']
טעות נפוצה: לא מוחקים איברים מרשימה תוך כדי מעבר עליה. כשמוחקים איבר, האיברים שאחריו זזים צעד אחורה, והלולאה מדלגת על האיבר הבא. בתוכנית הבאה רוצים למחוק את כל האפסים, ובכל זאת נשאר אחד, ולא הופיעה שום הודעת שגיאה:
samples = [5, 0, 0, 7, 0, 3]
for s in samples:
    if s == 0:
        samples.remove(s)
print(samples)
פלט
[5, 7, 0, 3]

הפתרון הברור ביותר הוא לבנות רשימה חדשה עם האיברים שרוצים להשאיר. פתרון אחר הוא לעבור על העתק ולמחוק מן המקורית: for s in samples[:]:.

samples = [5, 0, 0, 7, 0, 3]
clean = []
for s in samples:
    if s != 0:
        clean.append(s)
print(clean)
פלט
[5, 7, 3]
שאלה 6.8: מה מדפיסה התוכנית הבאה?
a = [8, 2, 6]
a.insert(0, 2)
a.append(4)
a.remove(2)
print(a)
a.sort()
print(a)
print(a.count(2))
a.extend([1, 1])
print(len(a))

6.11 רשימה של רשימות

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

012grid[0]102030grid[1]405060grid[1][2] = 60
grid[1] הוא השורה כולה, ו-grid[1][2] הוא איבר אחד ממנה
grid = [[10, 20, 30],
        [40, 50, 60]]
print(grid[1])
print(grid[1][2])
print(len(grid), len(grid[0]))
grid[0][1] = 25
print(grid)
פלט
[40, 50, 60]
60
2 3
[[10, 25, 30], [40, 50, 60]]

קוראים את grid[1][2] משמאל לימין: grid[1] הוא רשימה, ומתוכה לוקחים את האיבר [2]. len(grid) הוא מספר השורות, ו-len(grid[0]) הוא מספר העמודות. בתוכנית כותבים את הטבלה על כמה שורות כדי שתיראה כמו טבלה אמיתית, ופייתון מאפשרת זאת בתוך סוגריים.

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

grid = [[5, 1, 0],
        [2, 8, 4]]
for r in range(len(grid)):
    for c in range(len(grid[r])):
        print(grid[r][c], end=" ")
    print()
פלט
5 1 0 
2 8 4 
דוגמה 6.14
הציונים של שלושה תלמידים בשלושה ניסויי מעבדה: כל שורה היא תלמיד, וכל עמודה היא ניסוי. נדפיס את הממוצע של כל תלמיד, ואחר כך את הממוצע של כל ניסוי.
פתרון:
lab = [[78, 85, 90],
       [60, 72, 64],
       [95, 88, 91]]
for row in lab:
    print(row, round(sum(row) / len(row), 1))
for c in range(len(lab[0])):
    total = 0
    for r in range(len(lab)):
        total += lab[r][c]
    print("Experiment", c, round(total / len(lab), 1))
פלט
[78, 85, 90] 84.3
[60, 72, 64] 65.3
[95, 88, 91] 91.3
Experiment 0 77.7
Experiment 1 81.7
Experiment 2 81.7

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

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

rows = 2
cols = 3
grid = []
for r in range(rows):
    row = []
    for c in range(cols):
        row.append(r * 10 + c)
    grid.append(row)
print(grid)
פלט
[[0, 1, 2], [10, 11, 12]]
טעות נפוצה: זו בעיית ההשמה מסעיף 6.4 בלבוש חדש. הכתיב [[0] * 3] * 2 אינו יוצר שתי שורות, אלא חוזר פעמיים על השם של אותה שורה. משנים תא אחד והוא משתנה ב"שתי השורות":
grid = [[0] * 3] * 2
grid[0][0] = 7
print(grid)
פלט
[[7, 0, 0], [7, 0, 0]]
שאלה 6.9: מה מדפיסה התוכנית הבאה?
m = [[1, 2, 3],
     [4, 5, 6],
     [7, 8, 9]]
print(m[2][0], m[0][2])
d = 0
for i in range(len(m)):
    d += m[i][i]
print(d)
print(m[1][1:])

6.12 כתיבת אלגוריתמים (שאינם מובנים בשפה)

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

(א) האיבר הגדול ביותר:

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

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

volts = [12.1, 11.6, 12.9, 11.9]
pos = 0
for i in range(1, len(volts)):
    if volts[i] > volts[pos]:
        pos = i
print("Highest:", volts[pos], "at index", pos)
פלט
Highest: 12.9 at index 2
טעות נפוצה: מתחילים מאיבר של הרשימה, ולא מאפס. אם כל הערכים שליליים, כמו טמפרטורות בחורף, האפס נשאר "הגדול ביותר" אף שהוא כלל לא ברשימה:
temps = [-4, -9, -2]
top = 0
for t in temps:
    if t > top:
        top = t
print(top)
פלט
0

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

print("diode" < "resistor")
print("diode" < "diodes")
print("Zener" < "diode")
print("100" < "9")
print(100 < 9)
פלט
True
True
True
True
False

(ב) האיבר הקטן ביותר:

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

דוגמה 6.15
נמצא את הרכיב הראשון ואת הרכיב האחרון בסדר המילוני, כמו בקטלוג של מחסן. נבדוק את שני הכיוונים באותו סבב.
פתרון:
parts = ["resistor", "diode", "capacitor", "zener"]
first = parts[0]
last = parts[0]
for name in parts:
    if name < first:
        first = name
    if name > last:
        last = name
print(first)
print(last)
פלט
capacitor
zener

(ג) מיזוג שתי רשימות:

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

a = [3, 8, 15]
b = [1, 9, 11, 20]
merged = []
i = 0
j = 0
while i < len(a) and j < len(b):
    if a[i] <= b[j]:
        merged.append(a[i])
        i += 1
    else:
        merged.append(b[j])
        j += 1
merged += a[i:]
merged += b[j:]
print(merged)
פלט
[1, 3, 8, 9, 11, 15, 20]

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

הערה: אפשר לכתוב c = a + b ואז c.sort(), ולקבל את אותה תוצאה. אבל המיזוג עובר על כל איבר פעם אחת, ולכן הוא יעיל יותר כששתי הרשימות ממוינות.

(ד-1) מיון בועות:

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

j = 073915j = 137915j = 237915j = 337195nums37159
מעבר אחד: הזוג הצהוב החליף מקומות, הזוג הכחול נשאר כפי שהיה, והירוק במקומו הסופי

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

nums = [7, 3, 9, 1]
for j in range(3):
    if nums[j] > nums[j + 1]:
        nums[j], nums[j + 1] = nums[j + 1], nums[j]
print(nums)
מעקב אחר מעבר אחד
פקודהnumsjnums[j] > nums[j + 1]פלט
nums = [7, 3, 9, 1][7, 3, 9, 1]
for j in range(3):0
if nums[j] > nums[j + 1]:True
nums[j], nums[j + 1] = nums[j + 1], nums[j][3, 7, 9, 1]
for j in range(3):1
if nums[j] > nums[j + 1]:False
for j in range(3):2
if nums[j] > nums[j + 1]:True
nums[j], nums[j + 1] = nums[j + 1], nums[j][3, 7, 1, 9]
for j in range(3):
print(nums)[3, 7, 1, 9]

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

nums = [7, 3, 9, 1, 5]
n = len(nums)
for i in range(n - 1):
    for j in range(n - 1 - i):
        if nums[j] > nums[j + 1]:
            nums[j], nums[j + 1] = nums[j + 1], nums[j]
print(nums)
פלט
[1, 3, 5, 7, 9]
nums73915i = 037159i = 131579i = 213579i = 313579
הרשימה אחרי כל מעבר: האזור הירוק הממוין גדל מן הסוף

התנאי nums[j] > nums[j + 1] קובע את הכיוון, ולמיון בסדר יורד כותבים <. ומכיוון שהאופרטור > עובד גם על מחרוזות, אותה תוכנית ממיינת אותן לפי סדר מילוני.

טעות נפוצה: התחום בלולאה הפנימית הוא n - 1 - i. אם נכתוב n, אז nums[j + 1] יגיע אל מחוץ לרשימה, כי כל השוואה משתמשת גם באינדקס j + 1.
שאלה 6.10: מה מדפיסה התוכנית הבאה, שהיא מעבר אחד של מיון בועות? האם הרשימה נעשתה ממוינת?
v = [4, 2, 5, 1]
for j in range(3):
    if v[j] > v[j + 1]:
        v[j], v[j + 1] = v[j + 1], v[j]
print(v)

(ד-2) מיון החלפה:

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

nums = [7, 3, 9, 1, 5]
n = len(nums)
for i in range(n - 1):
    for j in range(i + 1, n):
        if nums[j] < nums[i]:
            nums[i], nums[j] = nums[j], nums[i]
print(nums)
פלט
[1, 3, 5, 7, 9]
nums73915i = 017935i = 113975i = 213597i = 313579
הרשימה אחרי כל סבב חיצוני: האזור הירוק הממוין גדל מן ההתחלה

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

for n in [5, 10, 100, 1000]:
    count = 0
    for i in range(n - 1):
        for j in range(n - 1 - i):
            count += 1
    print(n, count)
פלט
5 10
10 45
100 4950
1000 499500

פי עשרה איברים פירושו בערך פי מאה השוואות, כי המספר שווה ל-n(n−1)/2. לכן האלגוריתמים האלה אינם מתאימים למיליוני איברים, והפעולה sort המוכנה משתמשת באלגוריתם יעיל יותר. כותבים מיון בעצמנו כדי להבין איך הוא עובד, ובתוכנית רגילה משתמשים ב-sort.

6.13 אתחול טאפל, הפעולה tuple, הדפסה (tuple)

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

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

reading = (8, 12.4)
pin = ("A0", "input", 5)
empty = ()
one = (5,)
print(reading, pin)
print(empty, one, len(pin))
print(type(reading))
פלט
(8, 12.4) ('A0', 'input', 5)
() (5,) 3
<class 'tuple'>

הפעולה tuple ממירה לטאפל כפי ש-list ממירה לרשימה. הסוגריים אינם הכרחיים ביצירה: הפסיק הוא שיוצר את הטאפל. כבר פגשנו אותו בלי לקרוא לו בשם: הפעולה divmod בפרק 2 נותנת טאפל, וההשמה v, i = 12, 0.5 בונה אחד ואז מפרקת אותו:

print(tuple([1, 2, 3]))
print(tuple("ADC"))
print(tuple(range(4)))
pair = 12, 0.5
print(pair)
print(divmod(17, 5))
פלט
(1, 2, 3)
('A', 'D', 'C')
(0, 1, 2, 3)
(12, 0.5)
(3, 2)
טעות נפוצה: ניסיון לשנות טאפל עוצר את התוכנית, וזו המטרה שלו: הוא מגן על הערכים משינוי בטעות. וגם אין לו פעולה append:
reading = (8, 12.4)
reading[1] = 12.0
פלט
TypeError: 'tuple' object does not support item assignment
דוגמה 6.16
התברר שקריאה ביומן של שעה 8 שגויה, וצריך לתקן אותה. טאפל אי אפשר לשנות, ולכן נמיר אותו לרשימה, נשנה אותה ונבנה טאפל חדש.
פתרון:
reading = (8, 12.4)
temp = list(reading)
temp[1] = 12.0
reading = tuple(temp)
print(reading)
פלט
(8, 12.0)
שאלה 6.11: מה הטיפוס של כל ערך:
(א) (5)
(ב) (5,)
(ג) 5? הסבירו למה הראשון שונה מן השני.

6.14 המבנה הסידורי של טאפל, אינדקס וחיתוך

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

rec = (14, 12.6, "OK")
print(rec[0], rec[-1])
print(rec[1:])
hour, volts, status = rec
print(hour, volts, status)
פלט
14 OK
(12.6, 'OK')
14 12.6 OK
דוגמה 6.17
שמות הצבעים של נגדים לעולם אינם משתנים, ולכן הם מועמדים טובים לטאפל. ניקח נגד עם הספרות 4 ו-7 והמכפיל 2: נדפיס את צבעי הפסים שלו ואת ערכו.
פתרון:
colors = ("black", "brown", "red", "orange", "yellow",
          "green", "blue", "violet", "gray", "white")
first = 4
second = 7
power = 2
print(colors[first], colors[second], colors[power])
print((first * 10 + second) * 10 ** power, "ohm")
פלט
yellow violet red
4700 ohm

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

שאלה 6.12: מה מדפיסה התוכנית הבאה?
t = (2, 4, 6, 8, 10)
print(t[1], t[-1])
print(t[1:3])
print(t[::2])
a, b = t[:2]
print(a + b)

6.15 לולאת for העוברת על איברי טאפל

דוגמה 6.18
לולאת for עובדת כאן כמו ברשימה, על איברים ועל אינדקסים. והדבר החזק ביותר בטאפל הוא לשים אותו בתוך רשימה: כל טאפל הוא רשומה של קריאה אחת שאינה משתנה, והרשימה היא היומן כולו שגדל עם הזמן. נדפיס יומן קריאות, ונפרק כל טאפל ישירות בשורת הלולאה, וזה ברור יותר מכתיבת אינדקסים.
פתרון:
log = [(8, 12.4), (10, 12.7), (12, 13.1)]
print(log[1], log[1][0])
for hour, volts in log:
    print("Hour", hour, ":", volts, "V")
פלט
(10, 12.7) 10
Hour 8 : 12.4 V
Hour 10 : 12.7 V
Hour 12 : 13.1 V
שאלה 6.13: מה מדפיסה התוכנית הבאה?
log = [(1, 5), (2, 9), (3, 4)]
s = 0
for a, b in log:
    if a != 2:
        s += b
print(s)

6.16 עבודה עם פעולות המחלקה: count, index

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

bits = (1, 0, 1, 1, 0, 1)
print(bits.count(1))
print(bits.index(0), bits.index(0, 2))
print(bits.count(1) % 2)
פלט
4
1 4
0

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

דוגמה 6.19
חיפוש ערך שאינו קיים בפעולה index עוצר את התוכנית. לכן בודקים קודם באופרטור in. נחפש קו הזנה בין הקווים של הלוח.
פתרון:
rails = (3.3, 5, 12, 5)
wanted = 5
if wanted in rails:
    print(rails.index(wanted), rails.count(wanted))
else:
    print("No such rail")
פלט
1 2
שאלה 6.14: מה מדפיסה התוכנית הבאה?
days = (0, 2, 2, 5, 2, 0)
print(days.count(2))
print(days.index(2))
print(days.index(0, 1))
▶ניסוי חי — מעבדת מבני נתוניםנבנה רשימה, נעבור עליה בלולאת for צעד אחר צעד, ונראה את האינדקס והאיברים משתנים לנגד עינינו. במעבדה אין קלט מן המשתמש, ולכן כותבים את הערכים ישירות ברשימה במקום input.
⚠
המעבדה אינה זמינה כרגע. החומר בפרק שלם גם בלעדיה: ממשיכים לקרוא, וחוזרים לניסוי מאוחר יותר או פותחים אותו ישירות מן הקישור שמתחת למסגרת.
https://fahed-lab.com/electronics/python_basics#data
פותחים את המעבדה בדף נפרד ↗ fahed-lab.com/electronics/python_basics#data
פרויקט הספר: תחנת המדידה · שלב 6 — התחנה זוכרת

בפרק 4 קראה התחנה סדרת מדידות, אבל היא שכחה כל קריאה כשהגיעה הבאה. עכשיו היא זוכרת: כל מדידה הופכת לטאפל בצורה (hour, volts) שנשמר ברשימת היומן. רשימת מונים מסווגת את המדידות לשלושה תחומי מתח: נמוך מתחת ל-11.8 V, תקין עד 12.6 V, וגבוה מעל זה. ובסוף התחנה מחפשת את השעה של המתח הגבוה ביותר, וממיינת את הקריאות.

log = []
counts = [0, 0, 0]
hour = int(input("Hour (-1 to stop): "))
while hour != -1:
    volts = float(input("Battery V: "))
    log.append((hour, volts))
    if volts < 11.8:
        counts[0] += 1
    elif volts <= 12.6:
        counts[1] += 1
    else:
        counts[2] += 1
    hour = int(input("Hour (-1 to stop): "))
print(log)
print("Low, OK, High:", counts)
פלט
Hour (-1 to stop): 8
Battery V: 12.4
Hour (-1 to stop): 12
Battery V: 13.1
Hour (-1 to stop): 18
Battery V: 11.6
Hour (-1 to stop): -1
[(8, 12.4), (12, 13.1), (18, 11.6)]
Low, OK, High: [1, 1, 1]

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

log = [(8, 12.4), (12, 13.1), (18, 11.6)]
best = log[0]
readings = []
for rec in log:
    readings.append(rec[1])
    if rec[1] > best[1]:
        best = rec
print("Highest:", best[1], "V at hour", best[0])
readings.sort()
print("Sorted:", readings)
פלט
Highest: 13.1 V at hour 12
Sorted: [11.6, 12.4, 13.1]

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

עיקרי הפרק:

סיכום פקודות הפרק:

פקודהמה היא עושה
a = [1, 2] a = [] a = list(x)אתחול רשימה
a[i] a[-1] a[i][j]גישה לאיבר
a[start:stop:step] b = a[:]חיתוך והעתקה
a + b a += b [0] * nשרשור ושכפול
range(start, stop, step)סדרת מספרים שלמים
for v in a:מעבר על האיברים
for i in range(len(a)):מעבר על האינדקסים
x in a x not in aהאם הערך הוא איבר ברשימה?
a.append(x) a.insert(i, x) a.extend(b)הוספת איברים
a.remove(x) a.clear()מחיקת איברים
a.count(x) a.sort()מנייה ומיון
t = (1, 2) t = (5,) t = tuple(x)אתחול טאפל
t.count(x) t.index(x)שתי הפעולות של טאפל

מילון מונחים:

עבריתEnglishערבית
מבנה נתוניםData structureبنية المعطيات
רשימהListالقائمة
איברElementالعنصر
אינדקסIndexالمؤشّر
חיתוךSlicingالقصّ
רשימת מוניםCounter listقائمة عدّادات
רשימת צובריםAccumulator listقائمة مُجمِّعات
רשימה ריקהEmpty listالقائمة الفارغة
רשימה של רשימותList of listsقائمة من قوائم
ערך קיצוןExtreme valueقيمة قصوى
סדר מילוניLexicographic orderالترتيب المعجمي
מיזוגMergeالدمج
מיון בועותBubble sortالفرز الفقاعي
מיון החלפהExchange sortفرز التبديل
חציוןMedianالقيمة الوسطى
TupleTupleالصفّ الثابت
סיבית זוגיותParity bitخانة الزوجية
התשובות הסופיות לשאלות הפרק
6.1 (א) כן, בעזרת צובר. (ב) לא, כי את הממוצע יודעים רק בסוף, ואז הקריאות כבר אבדו. (ג) כן, בעזרת משתנה ששומר את ערך הקיצון.
6.2 [2, 0, 3, 1]
6.3 [7, 8, 3] , [7, 8, 3] , [1, 2, 9]
6.4 [1, 2, 4, 5] , [1, 2, 3] , [7, 7, 8]
6.5 0 , 7 , 7 , 16
6.6 2 , 2 , 1
6.7 3 , 5 , True , False
6.8 [8, 2, 6, 4] , [2, 4, 6, 8] , 1 , 6
6.9 7 3 , 15 , [5, 6]
6.10 [2, 4, 1, 5]. לא: רק האיבר הגדול ביותר הגיע למקומו, וצריך לפחות עוד מעבר אחד.
6.11 <class 'int'> , <class 'tuple'> , <class 'int'>. הסוגריים לבדם הם חשבוניים, והפסיק הוא שיוצר את הטאפל.
6.12 4 10 , (4, 6) , (2, 6, 10) , 6
6.13 9
6.14 3 , 1 , 5

שאלות לתרגול:

רמה ראשונה — בסיסי
1.
מה מדפיסה התוכנית?
v = [4, 8, 15, 16, 23, 42]
print(v[2], v[-2])
print(v[1:3])
print(v[::3])
print(len(v[2:]))
תשובות: 15 23 , [8, 15] , [4, 16] , 4
2.
כתבו תוכנית שקוראת ארבעה זרמים לרשימה, בלולאת for, ומדפיסה את הסכום שלהם ואת הגדול ביניהם.
תשובות:
currents = []
for k in range(4):
    currents.append(float(input("Current: ")))
print("Total:", sum(currents))
print("Largest:", max(currents))
עבור הערכים 1.5, 0.5, 2.25, 0.75 היא מדפיסה: Total: 5.0 , Largest: 2.25
3.
מצאו את שתי הטעויות בתוכנית הזאת ותקנו אותן.
volts = [12, 5, 3]
for i in range(len(volts) + 1):
    print(volts[i])
volts = volts.sort()
print(volts[0])
תשובות: הלולאה מגיעה לאינדקס שאינו קיים, והפעולה sort מחזירה None. והתוכנית הנכונה:
volts = [12, 5, 3]
for i in range(len(volts)):
    print(volts[i])
volts.sort()
print(volts[0])
והיא מדפיסה: 12 , 5 , 3 , 3
4.
הטאפל הוא t = (1, 2, 3). איזו מן הפקודות הבאות עוצרת את התוכנית?
(א) print(t[0])
(ב) t[0] = 5
(ג) print(t.count(2))
(ד) t.append(4)
תשובות: (ב) ו-(ד): אי אפשר לשנות טאפל, ואין לו פעולה append.
רמה שנייה — בינוני
5.
דגימות מחיישן מתח, מ-0 ועד פחות מ-5 V. כתבו תוכנית שמונה כמה דגימות נפלו בכל תחום ברוחב 1 V: מ-0 עד לפני 1, וכן הלאה. החלק השלם של הדגימה הוא האינדקס.
תשובות:
samples = [0.4, 1.2, 1.9, 3.3, 4.8, 1.1, 0.7, 3.9]
counts = [0, 0, 0, 0, 0]
for v in samples:
    counts[int(v)] += 1
print(counts)
היא מדפיסה: [2, 3, 0, 2, 1]
6.
כתבו תוכנית שמחליקה אות בממוצע נע: כל איבר ברשימה החדשה הוא הממוצע של שלוש דגימות רצופות. השתמשו בחיתוך.
תשובות:
samples = [2, 4, 9, 4, 2, 6]
smooth = []
for i in range(len(samples) - 2):
    window = samples[i:i + 3]
    smooth.append(round(sum(window) / 3, 2))
print(smooth)
היא מדפיסה: [5.0, 5.67, 5.0, 4.0]
7.
בספקטרום של אות, פסגה היא רמה שגבוהה משתי שכנותיה. כתבו תוכנית שמדפיסה את האינדקס של כל פסגה ואת הרמה שלה.
תשובות:
levels = [2, 7, 3, 4, 9, 1, 5, 2]
for i in range(1, len(levels) - 1):
    if levels[i - 1] < levels[i] > levels[i + 1]:
        print("Peak at index", i, "level", levels[i])
היא מדפיסה: Peak at index 1 level 7 , Peak at index 4 level 9 , Peak at index 6 level 5
8.
בנו טבלת מעקב לתוכנית הבאה.
a = [6, 3, 8]
b = []
for x in a:
    if x > 4:
        b.append(x - 4)
a[0] = len(b)
print(a, b)
תשובות:
פקודהabxx > 4פלט
a = [6, 3, 8][6, 3, 8]
b = [][]
for x in a:6
if x > 4:True
b.append(x - 4)[2]
for x in a:3
if x > 4:False
for x in a:8
if x > 4:True
b.append(x - 4)[2, 4]
for x in a:
a[0] = len(b)[2, 3, 8]
print(a, b)[2, 3, 8] [2, 4]
רמה שלישית — אתגר
9.
כתבו מיון בועות בסדר יורד שנעצר כשהוא עובר מעבר שלם בלי אף החלפה, ומדפיס את מספר המעברים. השתמשו במשתנה בוליאני.
תשובות:
nums = [3, 8, 2, 9, 4, 7]
n = len(nums)
passes = 0
swapped = True
while swapped:
    swapped = False
    passes += 1
    for j in range(n - 1):
        if nums[j] < nums[j + 1]:
            nums[j], nums[j + 1] = nums[j + 1], nums[j]
            swapped = True
print(nums)
print("Passes:", passes)
היא מדפיסה: [9, 8, 7, 4, 3, 2] , Passes: 4
10.
טבלה של שלושה ערוצים, ובכל שורה ארבע דגימות. כתבו תוכנית שמדפיסה לכל ערוץ את הממוצע שלו ואת הדגימה הגבוהה ביותר בו, ואז את מספר הערוץ עם הממוצע הגבוה ביותר. שימו לב שאין זה הערוץ עם הדגימה הגבוהה ביותר.
תשובות:
data = [[1.2, 1.4, 1.1, 1.3],
        [3.0, 3.3, 3.1, 3.2],
        [0.5, 4.9, 0.4, 0.6]]
best = 0
top = sum(data[0]) / len(data[0])
for ch in range(len(data)):
    average = sum(data[ch]) / len(data[ch])
    print("Channel", ch, round(average, 2), max(data[ch]))
    if average > top:
        top = average
        best = ch
print("Highest average: channel", best)
היא מדפיסה: Channel 0 1.25 1.4 , Channel 1 3.15 3.3 , Channel 2 1.6 4.9 , Highest average: channel 1
11.
הסיביות של בית שמורות בטאפל, מן הסיבית הגבוהה לנמוכה. כתבו תוכנית שמחשבת את ערך הבית ואת סיבית הזוגיות.
תשובות:
bits = (1, 0, 1, 1, 0, 0, 1, 0)
value = 0
for b in bits:
    value = value * 2 + b
print(value)
print("Parity bit:", bits.count(1) % 2)
היא מדפיסה: 178 , Parity bit: 0
12.
אוגר הזזה מעגלי: כתבו תוכנית שקוראת מספר צעדים, ומזיזה את הרשימה ימינה. מה שיוצא מן הסוף חוזר להתחלה. השתמשו בחיתוך.
תשובות:
reg = [1, 0, 0, 1, 1, 0]
k = int(input("Shift: "))
k = k % len(reg)
reg = reg[-k:] + reg[:-k]
print(reg)
עבור הקלט 2 היא מדפיסה: [1, 0, 1, 0, 0, 1]

שאלות בגרות לפרק 6

שתי השאלות הבאות מבחינת הבגרות נשענות על הפרק הזה ועל פרק 4, ונפתור אותן במלואן כתוכנית רגילה, כי עוד לא למדנו לכתוב פעולות.
בגרות 2026 שאלה 11 — הצבעה לחמישים שירים
בגרות 2026
סעיףהמבוקשתשובה
(א)קולטים בלולאה מספר שיר עד שנקלט מספר מחוץ לטווח, ומונים את ההצבעות ברשימת מונים.למטה
(ב)מציגים את מספר השיר שקיבל את מספר הקולות הגבוה ביותר, ואחד מהם אם יש כמה.למטה
(ג)מחשבים את ממוצע ההצבעות, ומציגים את מספרי השירים שקיבלו פחות ממנו.למטה
הפתרון המלא: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2026

(א) השירים ממוספרים מ-1 עד 50 והאינדקסים מ-0 עד 49, ולכן המונה של השיר נמצא בתא song - 1. תנאי הלולאה הוא הטווח עצמו, וכל מספר מחוץ לו מסיים אותה. (ב) מחפשים את האינדקס של הגדול ביותר כמו בסעיף 6.12, ומדפיסים את האינדקס ועוד 1. (ג) מחשבים את הממוצע מסכום ההצבעות, ואז עוברים על הרשימה שוב. זה בדיוק מה שאי אפשר לעשות בלי רשימה.

songs = 50
votes = [0] * songs
song = int(input("Song number: "))
while 1 <= song <= songs:
    votes[song - 1] += 1
    song = int(input("Song number: "))
best = 0
for i in range(1, songs):
    if votes[i] > votes[best]:
        best = i
print("Most votes: song", best + 1)
average = sum(votes) / songs
print("Average:", average)
print("Below average:")
for i in range(songs):
    if votes[i] < average:
        print(i + 1, end=" ")

חמישים שירים פירושם פלט ארוך שאי אפשר לבדוק בעין. לכן נשנה זמנית את השורה הראשונה ל-songs = 5, ונקליד 2, 3, 2, 1, 2, 3, 5, 4, 2, 3, 6. המספר 6 מחוץ לטווח ומסיים את הקליטה. התוכנית מדפיסה: Most votes: song 2 , Average: 2.0 , Below average: , 1 4 5

וכשיש שוויון, ההשוואה > בוחרת את השיר הראשון, כי השני אינו גדול ממנו, והשאלה מקבלת כל אחד מהם:

מעקב אחר סעיף (ב) עבור ארבעה שירים, ושניים מהם שווים
פקודהibestvotes[i] > votes[best]פלט
votes = [3, 7, 7, 1]
best = 00
for i in range(1, 4):1
if votes[i] > votes[best]:True
best = i1
for i in range(1, 4):2
if votes[i] > votes[best]:False
for i in range(1, 4):3
if votes[i] > votes[best]:False
for i in range(1, 4):
print("Most votes: song", best + 1)Most votes: song 2
בגרות 2025 שאלה 11 — חברת השליחויות
בגרות 2025
סעיףהמבוקשתשובה
קליטהקולטים בלולאה מספר שליח, מידת שביעות רצון וטיפ, ועוצרים במספר שליח לא חוקי.למטה
טיפיםמציגים כמה כסף קיבלו שני השליחים יחד כטיפים.למטה
הטוב ביותרמציגים את מספר השליח שקיבל את הדירוג הגבוה ביותר.למטה
בונוסמציגים 20% מן הטיפים של השליח הטוב ביותר.למטה
הפתרון המלא: fahed-lab.com/exams/electronics-815381/815381/solutions/solution-2025

לכל שליח שני ערכים שנצברים, ולכן בונים שתי רשימות צוברים מקבילות, והאינדקס courier - 1 מציין את אותו שליח בשתיהן. התנאי courier in [1, 2] מבטא את סוף הקליטה במקום שני תנאים שמחוברים באופרטור or.

tips = [0, 0]
ratings = [0, 0]
courier = int(input("Courier: "))
while courier in [1, 2]:
    ratings[courier - 1] += int(input("Rating (1-5): "))
    tips[courier - 1] += float(input("Tip: "))
    courier = int(input("Courier: "))
print("Total tips:", sum(tips))
if ratings[0] >= ratings[1]:
    best = 0
else:
    best = 1
print("Best courier:", best + 1)
print("Bonus:", tips[best] * 20 / 100)
פלט
Courier: 1
Rating (1-5): 5
Tip: 20
Courier: 2
Rating (1-5): 4
Tip: 30
Courier: 1
Rating (1-5): 2
Tip: 10
Courier: 0
Total tips: 60.0
Best courier: 1
Bonus: 6.0
הערה: השאלה אינה אומרת איך מחושב "הדירוג הגבוה ביותר", וגם לא מה עושים בשוויון. הנחנו שהדירוג הוא סכום מה שכל שליח קיבל, ובשוויון זוכה שליח 1. בבחינה כותבים הנחה כזאת במפורש ליד הפתרון. שימו לב: בקלט לדוגמה שלמעלה הממוצע היה מעניק את הניצחון לשליח 2 ולא לשליח 1. מי שמעדיף ממוצע צריך רשימת מונים שלישית, ובדיקה שמספר המשלוחים של השליח אינו אפס.