# dict — מילון: מפתח וערך<no value>

## מה נלמד?

**dict** (Dictionary, מילון) הוא מבנה שמחזיק **זוגות של מפתח וערך**. במקום לגשת לפי מיקום (`[0]`), ניגשים לפי **שם** (`["name"]`).

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

ב-tuple `("Dana", 24, "Haifa")` צריך **לזכור** שמיקום 1 הוא הגיל. עם 15 שדות, זה מתכון לבאגים. dict פותר את זה:

```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}
student["age"]   # clear and readable
```

dict הוא אולי המבנה **החשוב ביותר** לשבועות הבאים:

- **JSON** (מחר): הפורמט שבו שומרים קבצים ושולחים מידע ברשת. בפייתון הוא הופך ל-dict.
- **נושא Backend ומסדי נתונים**: כל בקשה ותשובה ב-API היא בעצם dict. `{"id": 7, "name": "Dana"}`.
- כל "רשומה", כמו משתמש, מוצר או הזמנה, נוח לייצג כ-dict.

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

### מהו dict?

- כל פריט הוא **זוג**: **מפתח** (Key) ‏←‏ **ערך** (Value).
- **מפתחות ייחודיים** — אין שני מפתחות זהים. השמה למפתח קיים **מחליפה** את הערך.
- **ניתן לשינוי** (Mutable).
- **שומר על סדר ההכנסה** (מ-Python 3.7).
- מפתח הוא בדרך כלל `str` או `int`. ערך יכול להיות **כל דבר**: מספר, טקסט, רשימה, ואפילו dict אחר.

{{< box "analogy" "אנלוגיה: מילון / ספר טלפונים" >}}
במילון אמיתי מחפשים **מילה** (מפתח) ומקבלים **הגדרה** (ערך). לא מחפשים "את ההגדרה במקום ה-5,000 בספר". בספר טלפונים: שם ‏←‏ מספר. אי אפשר ששני אנשים יופיעו עם בדיוק אותו שם ככניסות נפרדות. כניסה חדשה עם אותו שם מחליפה את הקודמת.
{{< /box >}}

## תחביר

```python
# create
empty = {}
student = {"name": "Dana", "age": 24}

# read
student["name"]                 # "Dana" - KeyError if the key does not exist
student.get("email")            # None - no error if missing
student.get("email", "N/A")     # "N/A" - default value if missing

# add / update (same syntax!)
student["email"] = "dana@mail.com"   # new key -> added
student["age"] = 25                  # existing key -> updated

# delete
del student["email"]            # delete a key (KeyError if missing)
age = student.pop("age")        # delete and return the value

# check
"name" in student               # True - checks KEYS, not values
len(student)                    # number of key-value pairs

# iterate
for key in student:             # keys
for value in student.values():  # values
for key, value in student.items():   # pairs (tuples!)
```

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

`week-2/student_dict.py`:

```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}

print("Name:", student["name"])
print("Age:", student["age"])

student["age"] = 25
student["email"] = "dana@mail.com"
print("Updated:", student)

del student["city"]
print("After delete:", student)

print("Has email?", "email" in student)
print("Phone:", student.get("phone", "not provided"))
```

**פלט:**

```text
Name: Dana
Age: 24
Updated: {'name': 'Dana', 'age': 25, 'city': 'Haifa', 'email': 'dana@mail.com'}
After delete: {'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
Has email? True
Phone: not provided
```

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

| שורה | הסבר |
|---|---|
| `{"name": "Dana", ...}` | מפתח, נקודתיים, ערך. זוגות מופרדים בפסיקים. |
| `student["name"]` | קריאה לפי מפתח: הערך `"Dana"`. |
| `student["age"] = 25` | המפתח **קיים**, ולכן הערך **מתעדכן**. |
| `student["email"] = "..."` | המפתח **לא קיים**, ולכן הוא **נוסף** (בסוף). אותו תחביר בדיוק! |
| `del student["city"]` | מחיקת הזוג כולו. |
| `"email" in student` | בודק אם **המפתח** קיים. |
| `student.get("phone", "not provided")` | המפתח לא קיים, ולכן מוחזרת ברירת המחדל, בלי שגיאה. |

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

**רשימה של מילונים** — המבנה הנפוץ ביותר לנתונים "אמיתיים" (טבלה: כל dict הוא שורה). וגם **ספירה** עם dict. `week-2/products.py`:

```python
products = [
    {"id": 1, "name": "Laptop", "price": 3500, "category": "electronics"},
    {"id": 2, "name": "Mouse", "price": 80, "category": "electronics"},
    {"id": 3, "name": "Desk", "price": 900, "category": "furniture"},
    {"id": 4, "name": "Chair", "price": 450, "category": "furniture"},
]

print("--- Catalog ---")
for product in products:
    print(product["id"], product["name"], product["price"], "NIS")

expensive = []
for product in products:
    if product["price"] > 500:
        expensive.append(product["name"])
print("Over 500 NIS:", expensive)

count_by_category = {}
for product in products:
    category = product["category"]
    count_by_category[category] = count_by_category.get(category, 0) + 1
print("Count by category:", count_by_category)

for category, count in count_by_category.items():
    print(category, "->", count, "products")
```

**פלט:**

```text
--- Catalog ---
1 Laptop 3500 NIS
2 Mouse 80 NIS
3 Desk 900 NIS
4 Chair 450 NIS
Over 500 NIS: ['Laptop', 'Desk']
Count by category: {'electronics': 2, 'furniture': 2}
electronics -> 2 products
furniture -> 2 products
```

**פירוק:**

- `products` היא **list** שכל איבר בה הוא **dict**. `product["price"]` = המחיר של המוצר הנוכחי בלולאה.
- **תבנית הספירה:** `count_by_category.get(category, 0) + 1`. אם הקטגוריה עדיין לא במילון, `get` מחזיר 0, ואז 0+1 = 1. אם היא כבר שם, מקבלים את הספירה הקיימת ומוסיפים 1. זו תבנית שחוזרת שוב ושוב.
- `.items()` — מחזיר זוגות `(key, value)` כ-tuples, ו-`for category, count in` מפרק אותם (Unpacking מהשיעור הקודם).

### dict בתוך dict

ערך יכול להיות dict בעצמו:

```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl 10"},
    "hobbies": ["chess", "running"],
}
print(user["address"]["city"])   # Haifa
print(user["hobbies"][0])        # chess
```

ניגשים "שכבה אחר שכבה": `user["address"]` מחזיר dict, ועליו `["city"]`. זה בדיוק המבנה של JSON.

## טעות נפוצה

**הטעות 1: `KeyError` על מפתח שלא קיים.**

```python
student = {"name": "Dana"}
print(student["age"])
```

```text
KeyError: 'age'
```

הפתרון: `student.get("age")` (מחזיר `None`), `student.get("age", 0)` (ברירת מחדל), או לבדוק קודם עם `if "age" in student:`.

**הטעות 2: לחפש ערך עם `in`.**

```python
student = {"name": "Dana"}
print("Dana" in student)
```

```text
False
```

`in` על dict בודק **מפתחות**. לבדיקת ערכים: `"Dana" in student.values()`.

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

```python
student = {"name": "Dana", "age": 24}
student["Age"] = 25
print(student)
```

```text
{'name': 'Dana', 'age': 24, 'Age': 25}
```

אין שגיאה! פשוט נוסף מפתח חדש `"Age"`, ו-`"age"` נשאר 24. מפתחות רגישים לאותיות גדולות וקטנות. זה Logical Error שקט, ולכן שומרים על שמות מפתחות עקביים (`snake_case`).

**הטעות 4: רשימה כמפתח.**

```python
locations = {[32, 35]: "Haifa"}
```

```text
TypeError: unhashable type: 'list'
```

מפתח חייב להיות **Immutable**. אם צריך מפתח של כמה ערכים, משתמשים ב-tuple: `{(32, 35): "Haifa"}`. (נסביר את הקשר ל-Immutable בשיעור הבא.)

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

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

### תרגיל 1: כרטיס סטודנט

צרו מילון `student` עם השם `"Dana"`, הגיל 24 והעיר `"Haifa"`. אחר כך:

1. עדכנו את הגיל ל-25.
2. הוסיפו אימייל `"dana@mail.com"`.
3. מחקו את העיר.
4. הדפיסו את המילון, ואת הטלפון בעזרת `get` עם ברירת המחדל `"none"`.

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

```text
{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none
```

{{< solution >}}
```python
student = {"name": "Dana", "age": 24, "city": "Haifa"}

student["age"] = 25
student["email"] = "dana@mail.com"
del student["city"]

print(student)
print(student.get("phone", "none"))
```

```text
{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none
```

`student["phone"]` היה זורק `KeyError`. `get` מחזיר ערך ברירת מחדל כשהמפתח לא קיים.
{{< /solution >}}

### תרגיל 2: ספירת מילים

כתבו פונקציה `count_words(text)` שמחזירה מילון: כל מילה (באותיות קטנות) ‏←‏ כמה פעמים היא מופיעה.

```python
counts = count_words("the cat and the dog and THE bird")
print(counts)
```

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

```text
{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}
```

{{< solution >}}
```python
def count_words(text):
    counts = {}
    for word in text.lower().split():
        counts[word] = counts.get(word, 0) + 1
    return counts


counts = count_words("the cat and the dog and THE bird")
print(counts)
```

```text
{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}
```

`counts.get(word, 0)` מחזיר 0 למילה שעוד לא נספרה — כך לא צריך `if` נפרד למילה חדשה.
{{< /solution >}}

### תרגיל 3: רשימה של מילונים

```python
products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]
```

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

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

```text
Mouse
Desk
{'electronics': 2, 'furniture': 1}
```

{{< solution >}}
```python
products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]

for product in products:
    if product["price"] < 1000:
        print(product["name"])

counts = {}
for product in products:
    category = product["category"]
    counts[category] = counts.get(category, 0) + 1
print(counts)
```

```text
Mouse
Desk
{'electronics': 2, 'furniture': 1}
```
{{< /solution >}}

### תרגיל 4: מעבר על מילון

```python
stock = {"apple": 12, "banana": 0, "cherry": 5}
```

עברו על המילון עם `items()` והדפיסו כל פריט ואת הכמות. לפריט שהכמות שלו 0 הדפיסו `out of stock`.

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

```text
apple -> 12
banana -> out of stock
cherry -> 5
```

{{< solution >}}
```python
stock = {"apple": 12, "banana": 0, "cherry": 5}

for name, quantity in stock.items():
    if quantity == 0:
        print(name, "->", "out of stock")
    else:
        print(name, "->", quantity)
```

```text
apple -> 12
banana -> out of stock
cherry -> 5
```

`items()` מחזיר זוגות של מפתח וערך, ו-Unpacking בלולאה מפרק כל זוג לשני משתנים.
{{< /solution >}}

### תרגיל 5: מילון מקונן

```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}
```

הדפיסו את העיר, את התחביב הראשון, והוסיפו תחביב `"music"`. בסוף הדפיסו את רשימת התחביבים.

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

```text
Haifa
chess
['chess', 'running', 'music']
```

{{< solution >}}
```python
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}

print(user["address"]["city"])
print(user["hobbies"][0])
user["hobbies"].append("music")
print(user["hobbies"])
```

```text
Haifa
chess
['chess', 'running', 'music']
```

כל `[ ]` נכנס רמה אחת פנימה: `user["address"]` הוא מילון, ו-`["city"]` שולף ממנו. זה בדיוק המבנה של JSON.
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **dict** `{key: value}` — גישה לפי **מפתח** ולא לפי מיקום. מפתחות ייחודיים.
- `d[key] = value` — **מוסיף** אם חדש, **מעדכן** אם קיים.
- `d[key]` זורק `KeyError` על מפתח חסר. `d.get(key, default)` לא זורק.
- `in` בודק **מפתחות**. מעבר: `keys()`, `values()`, `items()` (זוגות).
- **רשימה של מילונים** = טבלה. dict בתוך dict = JSON.
- תבנית ספירה: `counts[x] = counts.get(x, 0) + 1`.

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

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

- [Python Tutorial — Dictionaries](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) — dict
- [Built-in Types — Mapping Types (dict)](https://docs.python.org/3/builtins/stdtypes.html#mapping-types-dict-frozendict) — get, keys, values, items ועוד

<!-- docs:end -->
