# tuple ו-set — רשומה קבועה ואוסף בלי כפילויות<no value>

## מה נלמד?

שני מבני נתונים נוספים, כל אחד עם "כוח-על" משלו:

- **tuple** — כמו רשימה, אבל **קבוע**: אי אפשר לשנות אותו אחרי היצירה.
- **set** — אוסף **בלי כפילויות** ובלי סדר, שמאפשר בדיקה מהירה מאוד אם ערך קיים בו.

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

- **tuple** מתאים לנתונים שמייצגים **רשומה אחת עם מבנה קבוע**: נקודה `(x, y)`, תאריך `(2026, 10, 6)`, שורה מ-Database `(7, "Dana", 24)`. בנושא Backend ומסדי נתונים, **כל שורה שתחזור מה-Database תגיע כ-tuple!** הקביעות מבטיחה שאף קוד לא ישנה "בטעות" רשומה.
- **set** מתאים כשרוצים **ייחודיות**: כמה משתמשים **שונים** ביקרו באתר היום? אילו תגיות יש לפוסט? מי רשום לשני קורסים גם יחד?

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

### tuple

**tuple** הוא רצף **מסודר** של ערכים, **שלא ניתן לשינוי** (Immutable). אחרי שנוצר, אי אפשר להוסיף, למחוק או להחליף איברים.

- נכתב בסוגריים **עגולים**: `(1, 2, 3)`.
- גישה לאיברים כמו ברשימה: `point[0]`, `point[-1]`, `len(point)`, `in`.
- אין `append`, `remove`, ואין השמה לאיבר.

**Unpacking (פירוק):** אפשר "לפרק" tuple ישר למשתנים:

```python
student = ("Dana", 24, "Haifa")
name, age, city = student
```

כבר השתמשנו בזה בלי לשים לב: `for name, age in attempts:` ו-`for position, name in enumerate(...)`.

{{< box "analogy" "אנלוגיה" >}}
**list** היא כמו רשימת קניות על לוח מחיק: מוסיפים, מוחקים, משנים. **tuple** הוא כמו תאריך לידה בתעודת זהות: מודפס פעם אחת, ואף אחד לא אמור לשנות אותו.
{{< /box >}}

### set

**set** הוא אוסף של ערכים **ייחודיים**:

- **בלי כפילויות** — הוספה של ערך שכבר קיים לא עושה כלום.
- **בלי סדר** — אין אינדקסים. `my_set[0]` הוא שגיאה. הסדר בהדפסה יכול להיות כל סדר.
- **ניתן לשינוי** — אפשר להוסיף ולמחוק.
- **בדיקת `in` מהירה מאוד**, גם עם מיליון איברים. ברשימה, `in` עובר איבר-איבר.
- נכתב בסוגריים **מסולסלים**: `{1, 2, 3}`. **אבל** set ריק נוצר עם `set()`, כי `{}` הוא dict ריק (השיעור הבא).

### פעולות קבוצות

| פעולה | אופרטור | משמעות |
|---|---|---|
| איחוד (Union) | `a \| b` | כל מה שנמצא ב-a **או** ב-b |
| חיתוך (Intersection) | `a & b` | מה שנמצא **גם** ב-a **וגם** ב-b |
| הפרש (Difference) | `a - b` | מה שב-a **ולא** ב-b |

## תחביר

```python
# tuple
point = (3, 5)
single = (7,)              # a tuple with ONE item needs a comma!
x, y = point               # unpacking
point[0]                   # 3

# set
tags = {"python", "web"}
empty_set = set()          # NOT {} - that is an empty dict
tags.add("api")            # add one item
tags.remove("web")         # remove (KeyError if missing)
tags.discard("xyz")        # remove if present, no error if missing
"python" in tags           # fast membership test
unique = set([1, 1, 2])    # {1, 2} - remove duplicates from a list

a | b    # union
a & b    # intersection
a - b    # difference
```

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

`week-2/tuples_demo.py`:

```python
student = ("Dana", 24, "Haifa")

print("Full record:", student)
print("Name:", student[0])
print("Fields:", len(student))

name, age, city = student
print(name, "is", age, "and lives in", city)

student[1] = 25
```

**פלט:**

```text
Full record: ('Dana', 24, 'Haifa')
Name: Dana
Fields: 3
Dana is 24 and lives in Haifa
Traceback (most recent call last):
  File "tuples_demo.py", line 10, in <module>
    student[1] = 25
TypeError: 'tuple' object does not support item assignment
```

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

| שורה | הסבר |
|---|---|
| `student = ("Dana", 24, "Haifa")` | tuple עם 3 שדות. **המיקום קובע את המשמעות**: 0 = שם, 1 = גיל, 2 = עיר. |
| `student[0]` | גישה כמו ברשימה. |
| `name, age, city = student` | **Unpacking**: האיבר הראשון נכנס ל-`name`, השני ל-`age`, השלישי ל-`city`. מספר המשתנים חייב להיות שווה למספר האיברים. |
| `student[1] = 25` | ניסיון לשנות, ונזרק `TypeError`. **זו לא תקלה, זו ההגנה** שה-tuple נותן. |

רוצים "לשנות" tuple? יוצרים **חדש**: `student = (student[0], 25, student[2])`.

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

**ניתוח משתמשים באתר עם set.** `week-2/visitors.py`:

```python
monday_visits = ["dana", "avi", "dana", "noa", "avi", "dana"]
tuesday_visits = ["noa", "yossi", "rina", "noa"]

monday = set(monday_visits)
tuesday = set(tuesday_visits)

print("Monday page views:", len(monday_visits))
print("Monday unique visitors:", len(monday), monday)

print("Visited on both days:", monday & tuesday)
print("Visited on any day:", monday | tuesday)
print("Only on Monday:", monday - tuesday)

tuesday.add("dana")
tuesday.add("noa")
print("Tuesday after adds:", len(tuesday), "unique")
print("Did yossi visit on Monday?", "yossi" in monday)
```

**פלט (סדר האיברים בתוך `{}` יכול להיות שונה אצלכם):**

```text
Monday page views: 6
Monday unique visitors: 3 {'avi', 'noa', 'dana'}
Visited on both days: {'noa'}
Visited on any day: {'dana', 'yossi', 'rina', 'avi', 'noa'}
Only on Monday: {'avi', 'dana'}
Tuesday after adds: 4 unique
Did yossi visit on Monday? False
```

**פירוק:**

- `set(monday_visits)` — ממיר רשימה ל-set, והכפילויות נעלמות: 6 צפיות, 3 מבקרים.
- `monday & tuesday` — **חיתוך**: רק מי שמופיע בשניהם (`noa`).
- `monday | tuesday` — **איחוד**: כל מי שביקר, כל אחד פעם אחת.
- `monday - tuesday` — **הפרש**: ביקרו ביום שני ולא ביום שלישי.
- `tuesday.add("noa")` — `noa` כבר קיימת, ולכן לא קורה כלום. נוספה רק `dana`, ויש 4 (`noa`, `yossi`, `rina`, `dana`).

### מתי מה? טבלת השוואה

| | list | tuple | set |
|---|---|---|---|
| כתיבה | `[1, 2]` | `(1, 2)` | `{1, 2}` |
| סדר / אינדקס | ✓ | ✓ | ✗ |
| שינוי | ✓ | ✗ | ✓ |
| כפילויות | ✓ | ✓ | ✗ |
| שימוש טיפוסי | אוסף שגדל ומשתנה | רשומה עם מבנה קבוע | ייחודיות ובדיקת שייכות |

## טעות נפוצה

**הטעות 1: tuple של איבר אחד בלי פסיק.**

```python
colors = ("red")
print(type(colors))
```

```text
<class 'str'>
```

סוגריים לבד הם רק סוגריים של חשבון, כמו `(2 + 3)`. כדי ליצור tuple של איבר אחד: `("red",)`. **הפסיק יוצר את ה-tuple**, לא הסוגריים.

**הטעות 2: `{}` כ-set ריק.**

```python
seen = {}
seen.add("dana")
```

```text
AttributeError: 'dict' object has no attribute 'add'
```

`{}` הוא **dict** ריק. set ריק = `set()`.

**הטעות 3: לצפות לסדר או לאינדקס ב-set.**

```python
names = {"dana", "avi"}
print(names[0])
```

```text
TypeError: 'set' object is not subscriptable
```

ל-set אין "ראשון". אם צריך סדר, ממירים לרשימה ממוינת: `sorted(names)`.

**הטעות 4: Unpacking עם מספר משתנים לא תואם.**

```python
name, age = ("Dana", 24, "Haifa")
```

```text
ValueError: too many values to unpack (expected 2)
```

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

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

### תרגיל 1: Unpacking

```python
student = ("Dana", 24, "Haifa")
```

פרקו את ה-tuple לשלושה משתנים בשורה אחת, והדפיסו משפט עליהם. אחר כך נסו לשנות את הגיל ל-25 — מה קורה?

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

```text
Dana is 24 and lives in Haifa
```

{{< solution >}}
```python
student = ("Dana", 24, "Haifa")

name, age, city = student
print(name, "is", age, "and lives in", city)
```

```text
Dana is 24 and lives in Haifa
```

`student[1] = 25` יזרוק `TypeError: 'tuple' object does not support item assignment` — tuple הוא **Immutable**. אם צריך לשנות, יוצרים tuple חדש: `student = (name, 25, city)`.
{{< /solution >}}

### תרגיל 2: פונקציה שמחזירה שני ערכים

כתבו פונקציה `min_max(numbers)` שמחזירה tuple של הקטן והגדול ביותר. פרקו את התוצאה לשני משתנים.

```python
lowest, highest = min_max([40, 3, 99, 17])
print(lowest, highest)
```

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

```text
3 99
```

{{< solution >}}
```python
def min_max(numbers):
    return (min(numbers), max(numbers))


lowest, highest = min_max([40, 3, 99, 17])
print(lowest, highest)
```

```text
3 99
```

פונקציה מחזירה תמיד ערך **אחד** — כאן tuple. ה-Unpacking בצד של הקורא הופך אותו לשני משתנים.
{{< /solution >}}

### תרגיל 3: מבקרים ייחודיים

```python
visits = ["dana", "avi", "dana", "noa", "avi", "dana"]
```

הדפיסו כמה צפיות היו, כמה מבקרים **שונים** היו, ואם `"tom"` ביקר.

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

```text
6 views
3 visitors
False
```

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

unique = set(visits)
print(len(visits), "views")
print(len(unique), "visitors")
print("tom" in unique)
```

```text
6 views
3 visitors
False
```

`set` לא מחזיק כפילויות, ולכן `len(set(...))` סופר כמה ערכים שונים יש. בדיקת `in` על set גם מהירה מאוד.
{{< /solution >}}

### תרגיל 4: פעולות קבוצות

```python
football = {"dana", "avi", "noa", "tom"}
chess = {"noa", "rina", "tom", "yossi"}
```

1. מי בשני החוגים?
2. מי רק בכדורגל?
3. כמה ילדים שונים יש בסך הכול?

הדפיסו את התשובות 1 ו-2 ממוינות, כדי שהסדר יהיה קבוע.

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

```text
['noa', 'tom']
['avi', 'dana']
6
```

{{< solution >}}
```python
football = {"dana", "avi", "noa", "tom"}
chess = {"noa", "rina", "tom", "yossi"}

print(sorted(football & chess))
print(sorted(football - chess))
print(len(football | chess))
```

```text
['noa', 'tom']
['avi', 'dana']
6
```

- `&` — חיתוך (בשתיהן). `-` — הפרש (רק בראשונה). `|` — איחוד (באחת מהן לפחות).
- ל-set אין סדר קבוע, ולכן `sorted` מחזיר רשימה ממוינת להדפסה.
{{< /solution >}}

### תרגיל 5: list, tuple או set?

לכל מקרה — באיזה מבנה הייתם משתמשים? הסבירו.

1. קואורדינטות של נקודה על מפה: רוחב ואורך.
2. רשימת משימות שמוסיפים לה ומוחקים ממנה, לפי סדר.
3. כתובות האימייל של כל מי שנרשם — בלי כפילויות.
4. התור בקופה בסופרמרקט.

{{< solution >}}
1. **tuple** — רשומה קבועה של שני ערכים שלא אמורים להשתנות: `(32.08, 34.78)`.
2. **list** — סדר חשוב, ומוסיפים ומוחקים.
3. **set** — בלי כפילויות, ובדיקה מהירה אם כתובת כבר קיימת.
4. **list** — הסדר חשוב, ושולפים מההתחלה (`pop(0)`).
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **tuple** `( , )` — מסודר, **לא ניתן לשינוי**. מתאים לרשומה עם מבנה קבוע. איבר אחד: `(x,)`.
- **Unpacking**: `a, b = (1, 2)`. ככה מחזירים ומקבלים כמה ערכים מפונקציה.
- **set** `{ }` — בלי כפילויות, בלי סדר, בלי אינדקס. set ריק = `set()`.
- `set(list)` מסיר כפילויות. `in` על set מהיר מאוד.
- פעולות קבוצות: `|` איחוד, `&` חיתוך, `-` הפרש.
- בנושא Backend ומסדי נתונים, שורות מ-Database יחזרו כ-**tuple**.

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

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

- [Python Tutorial — Tuples and Sequences](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences) — tuple
- [Python Tutorial — Sets](https://docs.python.org/3/tutorial/datastructures.html#sets) — set
- [Built-in Types — Set Types](https://docs.python.org/3/builtins/stdtypes.html#set-types-set-frozenset) — כל הפעולות על set

<!-- docs:end -->
