# list — רשימה: יצירה, גישה, הוספה, מחיקה, חיפוש ומעבר<no value>

## מה נלמד?

**list** (רשימה) היא מבנה הנתונים הנפוץ ביותר ב-Python. כבר השתמשנו בה בקצרה. היום נלמד אותה כמו שצריך:

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

## למה צריך את זה?

כמעט כל תוכנה מחזיקה **אוספים**: רשימת סטודנטים, מוצרים בעגלה, הזמנות של היום, שורות מקובץ, תוצאות משאילתת Database (נושא Backend ומסדי נתונים). כשהמספר לא ידוע מראש, אי אפשר להחזיק משתנה נפרד לכל פריט (`student1`, `student2`...). רשימה מחזיקה **כמה שצריך**, בסדר מסוים.

## הסבר מהבסיס

### מהי list?

**list** היא אוסף **מסודר** של ערכים:

- **מסודר** (Ordered) — לכל איבר יש **מיקום** קבוע, ה**אינדקס** (Index).
- **ניתן לשינוי** (Mutable) — אפשר להוסיף, למחוק ולשנות איברים אחרי היצירה.
- **מאפשר כפילויות** — אותו ערך יכול להופיע כמה פעמים.
- **כל טיפוס** — אפשר לערבב (`[1, "a", True]`), אבל בדרך כלל רשימה מחזיקה פריטים מאותו סוג.

{{< box "analogy" "אנלוגיה: רכבת" >}}
רשימה היא רכבת עם קרונות ממוספרים. כל קרון (איבר) נושא מטען (ערך). הקרון הראשון הוא **מספר 0** ולא 1. אפשר לחבר קרון בסוף (`append`), להכניס קרון באמצע (`insert`), או לנתק קרון (`remove`/`pop`).
{{< /box >}}

### אינדקסים

```text
fruits = ["apple", "banana", "cherry", "date"]
index:       0         1         2        3
negative:   -4        -3        -2       -1
```

- הספירה מתחילה מ-**0**.
- אינדקס **שלילי** סופר מהסוף: `-1` = האחרון, `-2` = לפני האחרון.
- אינדקס שלא קיים ‏←‏ `IndexError`.

## תחביר

```python
# create
empty = []
numbers = [10, 20, 30]

# access and change
numbers[0]          # 10 - first item
numbers[-1]         # 30 - last item
numbers[1] = 25     # change an item
len(numbers)        # 3 - how many items

# add
numbers.append(40)       # add to the end
numbers.insert(0, 5)     # insert at index 0, pushing the rest right

# remove
numbers.remove(25)       # remove the FIRST item equal to 25
last = numbers.pop()     # remove and return the last item
first = numbers.pop(0)   # remove and return the item at index 0
del numbers[0]           # delete by index (returns nothing)
numbers.clear()          # remove everything

# search
30 in numbers            # True / False
numbers.index(30)        # index of the first 30 (ValueError if missing)
numbers.count(30)        # how many times 30 appears

# sort
numbers.sort()               # sorts THIS list in place
numbers.sort(reverse=True)   # descending
sorted(numbers)              # returns a NEW sorted list, original unchanged
```

**מתודה** (Method) היא פונקציה ש"שייכת" לאובייקט, ונקראת עם נקודה: `numbers.append(40)`. נלמד על מתודות לעומק בנושא OOP. כרגע: "פעולה שהרשימה יודעת לעשות על עצמה".

## דוגמת קוד ראשונה

`week-2/shopping_list.py`:

```python
shopping = ["milk", "bread", "eggs"]
print("Start:", shopping)

shopping.append("cheese")
print("After append:", shopping)

shopping.insert(1, "butter")
print("After insert:", shopping)

shopping.remove("bread")
print("After remove:", shopping)

print("First item:", shopping[0])
print("Last item:", shopping[-1])
print("Items count:", len(shopping))
```

**פלט:**

```text
Start: ['milk', 'bread', 'eggs']
After append: ['milk', 'bread', 'eggs', 'cheese']
After insert: ['milk', 'butter', 'bread', 'eggs', 'cheese']
After remove: ['milk', 'butter', 'eggs', 'cheese']
First item: milk
Last item: cheese
Items count: 4
```

## פירוק הקוד שורה אחר שורה

| שורה | מה קרה לרשימה |
|---|---|
| `shopping = [...]` | יצירה עם 3 איברים: אינדקסים 0, 1, 2. |
| `.append("cheese")` | נוסף **בסוף**. עכשיו 4 איברים. |
| `.insert(1, "butter")` | נכנס **באינדקס 1**. מה שהיה שם (`bread`) ומה שאחריו זזו ימינה. |
| `.remove("bread")` | נמחק **לפי ערך**: Python מחפשת את המופע הראשון של `"bread"` ומוחקת אותו. |
| `shopping[0]` | האיבר הראשון. |
| `shopping[-1]` | האיבר האחרון, בלי לדעת כמה יש. |
| `len(shopping)` | מספר האיברים. |

**שימו לב:** `append`, `insert` ו-`remove` **משנות את הרשימה עצמה** ולא מחזירות רשימה חדשה. לכן לא כותבים `shopping = shopping.append(...)` (על זה, בסעיף הטעויות).

## דוגמה נוספת

**ניהול תור לקופה**: מעבר, חיפוש, `pop` ו-`enumerate`. `week-2/queue.py`:

```python
queue = ["dana", "avi", "noa", "avi", "yossi"]

print("People in queue:", len(queue))
print("Is noa waiting?", "noa" in queue)
print("noa's position:", queue.index("noa") + 1)
print("How many times avi:", queue.count("avi"))

served = queue.pop(0)
print("Now serving:", served)

print("--- Remaining queue ---")
for position, name in enumerate(queue, start=1):
    print(position, name)

queue.sort()
print("Alphabetical:", queue)
```

**פלט:**

```text
People in queue: 5
Is noa waiting? True
noa's position: 3
How many times avi: 2
Now serving: dana
--- Remaining queue ---
1 avi
2 noa
3 avi
4 yossi
Alphabetical: ['avi', 'avi', 'noa', 'yossi']
```

**פירוק:**

- `"noa" in queue` — `in` עובר על הרשימה ובודק אם הערך קיים. מחזיר `bool`.
- `queue.index("noa") + 1` — `index` מחזיר את האינדקס (2). מוסיפים 1 כי לבני אדם "מקום שלישי" מתחיל מ-1.
- `queue.pop(0)` — **מוציא ומחזיר** את הראשון. זה ההבדל מ-`remove`: `pop` נותן את הערך שהוצא, כדי שנוכל להשתמש בו (`served`).
- `enumerate(queue, start=1)` — בכל סיבוב נותן **זוג**: מספר סידורי והאיבר. `for position, name in ...` מפרק את הזוג לשני משתנים. בלי `start=1`, הספירה מתחילה מ-0.
- `queue.sort()` — ממיין את הרשימה **עצמה**, בסדר אלפביתי לטקסט ובסדר עולה למספרים.

## טעות נפוצה

**הטעות 1: להשים את התוצאה של מתודה שמשנה במקום.**

```python
names = ["noa", "avi"]
names = names.append("dana")
print(names)
```

```text
None
```

`append` משנה את הרשימה ו**מחזירה `None`**. השמנו את ה-`None` לתוך `names`, ואיבדנו את הרשימה! התיקון: `names.append("dana")` לבד. אותו דבר עם `sort()`, `insert()`, `remove()`.

**הטעות 2: Off-by-one באינדקס האחרון.**

```python
items = ["a", "b", "c"]
print(items[len(items)])
```

```text
IndexError: list index out of range
```

`len` הוא 3, אבל האינדקס האחרון הוא **2**. האחרון = `items[-1]` או `items[len(items) - 1]`.

**הטעות 3: `remove` על ערך שלא קיים.**

```python
items.remove("z")
```

```text
ValueError: list.remove(x): x not in list
```

בודקים קודם: `if "z" in items: items.remove("z")`.

**הטעות 4: למחוק מרשימה תוך כדי מעבר עליה.**

```python
numbers = [1, 2, 2, 3]
for n in numbers:
    if n == 2:
        numbers.remove(n)
print(numbers)
```

```text
[1, 2, 3]
```

ציפינו ל-`[1, 3]`. כשמוחקים איבר, כל האיברים זזים שמאלה, והלולאה "מדלגת" על האיבר הבא. הפתרון הבטוח: **לבנות רשימה חדשה** עם מה שרוצים לשמור:

```python
numbers = [1, 2, 2, 3]
kept = []
for n in numbers:
    if n != 2:
        kept.append(n)
print(kept)   # [1, 3]
```

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

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

### תרגיל 1: אינדקסים

```python
days = ["Sun", "Mon", "Tue", "Wed", "Thu"]
```

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

**פלט צפוי:**

```text
Sun
Thu
Tue
```

{{< solution >}}
```python
days = ["Sun", "Mon", "Tue", "Wed", "Thu"]

print(days[0])
print(days[-1])
print(days[2])
```

```text
Sun
Thu
Tue
```

אינדקסים מתחילים מ-0. אינדקס שלילי סופר מהסוף: `-1` הוא האיבר האחרון.
{{< /solution >}}

### תרגיל 2: רשימת קניות

התחילו מ-`["milk", "bread"]`, ובצעו לפי הסדר:

1. הוסיפו `"eggs"` לסוף.
2. הוסיפו `"butter"` במקום הראשון.
3. מחקו את `"bread"`.
4. הדפיסו את הרשימה ואת מספר הפריטים.

**פלט צפוי:**

```text
['butter', 'milk', 'eggs']
3
```

{{< solution >}}
```python
shopping = ["milk", "bread"]

shopping.append("eggs")
shopping.insert(0, "butter")
shopping.remove("bread")

print(shopping)
print(len(shopping))
```

```text
['butter', 'milk', 'eggs']
3
```

- `append` — לסוף. `insert(0, ...)` — למקום מסוים.
- `remove` — מוחק **לפי ערך** (את המופע הראשון).
{{< /solution >}}

### תרגיל 3: תור

```python
queue = ["dana", "avi", "noa", "avi", "yossi"]
```

1. בדקו אם `"noa"` בתור, והדפיסו באיזה מקום היא (המקום הראשון הוא 1).
2. הדפיסו כמה פעמים `"avi"` מופיע.
3. שלפו את הראשון בתור והדפיסו `Now serving: <name>`, ואת מספר האנשים שנשארו.

**פלט צפוי:**

```text
True
3
2
Now serving: dana
4
```

{{< solution >}}
```python
queue = ["dana", "avi", "noa", "avi", "yossi"]

print("noa" in queue)
print(queue.index("noa") + 1)
print(queue.count("avi"))

served = queue.pop(0)
print("Now serving:", served)
print(len(queue))
```

```text
True
3
2
Now serving: dana
4
```

`pop(0)` גם מוחק וגם **מחזיר** את האיבר — לכן אפשר לשמור אותו במשתנה.
{{< /solution >}}

### תרגיל 4: סטטיסטיקה על ציונים

```python
grades = [78, 92, 55, 88, 100, 43, 67]
```

הדפיסו את הציון הגבוה, את הנמוך, את הממוצע (מעוגל לספרה אחת), ורשימה חדשה של הציונים שעברו (55 ומעלה) — בלי לשנות את `grades`.

**פלט צפוי:**

```text
Highest: 100
Lowest: 43
Average: 74.7
Passed: [78, 92, 55, 88, 100, 67]
```

{{< solution >}}
```python
PASSING_GRADE = 55

grades = [78, 92, 55, 88, 100, 43, 67]

print("Highest:", max(grades))
print("Lowest:", min(grades))
print("Average:", round(sum(grades) / len(grades), 1))

passed = []
for grade in grades:
    if grade >= PASSING_GRADE:
        passed.append(grade)
print("Passed:", passed)
```

```text
Highest: 100
Lowest: 43
Average: 74.7
Passed: [78, 92, 55, 88, 100, 67]
```

הדפוס של "רשימה חדשה + לולאה + `append`" הוא הדרך לסנן רשימה בלי לגעת במקורית.
{{< /solution >}}

### תרגיל 5: מחיקה בזמן לולאה

```python
numbers = [1, 2, 2, 3, 2, 4]
for n in numbers:
    if n == 2:
        numbers.remove(n)
print(numbers)
```

הכוונה הייתה למחוק את כל ה-2, אבל אחד נשאר. כתבו פונקציה `remove_all(items, value)` שמחזירה רשימה חדשה בלי אף מופע של `value`.

**פלט צפוי:**

```text
[1, 3, 4]
```

{{< solution >}}
כשמוחקים מרשימה **תוך כדי** מעבר עליה, האיברים זזים שמאלה, והלולאה מדלגת על האיבר שהגיע למקום שנמחק. לכן בונים רשימה חדשה במקום:

```python
def remove_all(items, value):
    result = []
    for item in items:
        if item != value:
            result.append(item)
    return result


print(remove_all([1, 2, 2, 3, 2, 4], 2))
```

```text
[1, 3, 4]
```
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **list** = אוסף מסודר, ניתן לשינוי, עם כפילויות. נכתבת ב-`[ ]`.
- אינדקס מתחיל ב-**0**. `-1` = האחרון. אינדקס לא קיים ‏←‏ `IndexError`.
- הוספה: `append` (לסוף), `insert` (למקום). מחיקה: `remove` (לפי ערך), `pop` (לפי מיקום, ומחזירה), `del`.
- חיפוש: `in`, `index`, `count`. מעבר: `for`, ו-`enumerate` כשצריך גם מספר.
- מתודות שמשנות במקום (`append`, `sort`...) מחזירות **`None`**. `sorted()` מחזירה רשימה **חדשה**.
- לא מוחקים מרשימה תוך כדי מעבר עליה. בונים רשימה חדשה.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## דוקומנטציה רשמית

- [Python Tutorial — Lists](https://docs.python.org/3/tutorial/introduction.html#lists) — היכרות עם list
- [Python Tutorial — More on Lists](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) — append, remove, index ועוד
- [Built-in Types — Lists](https://docs.python.org/3/builtins/stdtypes.html#lists) — ההגדרה המלאה של list

<!-- docs:end -->
