# tuple and set: a Fixed Record and a Collection Without Duplicates<no value>

## What will we learn?

Two more data structures, each with a "superpower" of its own:

- **tuple** — like a list, but **fixed**: it cannot be changed after it is created.
- **set** — a collection **without duplicates** and without order, which lets you check very quickly whether a value is in it.

## Why do we need this?

- A **tuple** suits data that represents **one record with a fixed structure**: a point `(x, y)`, a date `(2026, 10, 6)`, a row from a Database `(7, "Dana", 24)`. In the backend and databases topics, **every row that comes back from the Database will arrive as a tuple!** Being fixed guarantees that no code changes a record "by mistake".
- A **set** suits cases where you want **uniqueness**: how many **different** users visited the site today? Which tags does a post have? Who is registered for two courses at once?

## Explanation from the basics

### tuple

A **tuple** is an **ordered** sequence of values, **that cannot be changed** (Immutable). Once it is created, you cannot add, remove or replace items.

- Written with **round** brackets: `(1, 2, 3)`.
- Accessing items is like in a list: `point[0]`, `point[-1]`, `len(point)`, `in`.
- There is no `append`, no `remove`, and no assigning to an item.

**Unpacking:** you can "unpack" a tuple straight into variables:

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

We already used this without noticing: `for name, age in attempts:` and `for position, name in enumerate(...)`.

{{< box "analogy" "Analogy" >}}
A **list** is like a shopping list on a whiteboard: you add, erase, change. A **tuple** is like the date of birth on an ID card: printed once, and nobody is supposed to change it.
{{< /box >}}

### set

A **set** is a collection of **unique** values:

- **No duplicates** — adding a value that already exists does nothing.
- **No order** — there are no indexes. `my_set[0]` is an error. The order when printing can be any order.
- **Mutable** — you can add and remove.
- **A very fast `in` check**, even with a million items. In a list, `in` goes item by item.
- Written with **curly** brackets: `{1, 2, 3}`. **But** an empty set is created with `set()`, because `{}` is an empty dict (the next lesson).

### Set operations

| Operation | Operator | Meaning |
|---|---|---|
| Union | `a \| b` | Everything that is in a **or** in b |
| Intersection | `a & b` | What is **both** in a **and** in b |
| Difference | `a - b` | What is in a **and not** in b |

## Syntax

```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
```

## First code example

`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
```

**Output:**

```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
```

## Breaking down the code line by line

| Line | Explanation |
|---|---|
| `student = ("Dana", 24, "Haifa")` | A tuple with 3 fields. **The position sets the meaning**: 0 = name, 1 = age, 2 = city. |
| `student[0]` | Access like in a list. |
| `name, age, city = student` | **Unpacking**: the first item goes into `name`, the second into `age`, the third into `city`. The number of variables must equal the number of items. |
| `student[1] = 25` | An attempt to change it, and a `TypeError` is thrown. **This is not a fault, it is the protection** the tuple gives. |

Want to "change" a tuple? Create a **new** one: `student = (student[0], 25, student[2])`.

## Another example

**Analyzing website users with a 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)
```

**Output (the order of the items inside `{}` may be different for you):**

```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
```

**Breakdown:**

- `set(monday_visits)` — converts a list to a set, and the duplicates disappear: 6 views, 3 visitors.
- `monday & tuesday` — **intersection**: only whoever appears in both (`noa`).
- `monday | tuesday` — **union**: everyone who visited, each one once.
- `monday - tuesday` — **difference**: visited on Monday and not on Tuesday.
- `tuesday.add("noa")` — `noa` already exists, so nothing happens. Only `dana` was added, and there are 4 (`noa`, `yossi`, `rina`, `dana`).

### When to use which? A comparison table

| | list | tuple | set |
|---|---|---|---|
| Written as | `[1, 2]` | `(1, 2)` | `{1, 2}` |
| Order / index | ✓ | ✓ | ✗ |
| Can change | ✓ | ✗ | ✓ |
| Duplicates | ✓ | ✓ | ✗ |
| Typical use | A collection that grows and changes | A record with a fixed structure | Uniqueness and membership checks |

## Common mistake

**Mistake 1: a tuple of one item without a comma.**

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

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

Brackets alone are just arithmetic brackets, like `(2 + 3)`. To create a tuple of one item: `("red",)`. **The comma creates the tuple**, not the brackets.

**Mistake 2: `{}` as an empty set.**

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

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

`{}` is an empty **dict**. An empty set = `set()`.

**Mistake 3: expecting order or an index in a set.**

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

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

A set has no "first". If you need order, convert it to a sorted list: `sorted(names)`.

**Mistake 4: Unpacking with a number of variables that does not match.**

```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/ -->
## Practice

The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.

### Exercise 1: Unpacking

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

Unpack the tuple into three variables in one line, and print a sentence about them. Then try to change the age to 25 — what happens?

**Expected output:**

```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` will throw `TypeError: 'tuple' object does not support item assignment` — a tuple is **Immutable**. If you need to change it, create a new tuple: `student = (name, 25, city)`.
{{< /solution >}}

### Exercise 2: A function that returns two values

Write a function `min_max(numbers)` that returns a tuple of the smallest and the largest. Unpack the result into two variables.

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

**Expected output:**

```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
```

A function always returns **one** value — here a tuple. The Unpacking on the caller's side turns it into two variables.
{{< /solution >}}

### Exercise 3: Unique visitors

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

Print how many views there were, how many **different** visitors there were, and whether `"tom"` visited.

**Expected output:**

```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
```

A `set` does not hold duplicates, so `len(set(...))` counts how many different values there are. An `in` check on a set is also very fast.
{{< /solution >}}

### Exercise 4: Set operations

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

1. Who is in both clubs?
2. Who is only in football?
3. How many different children are there in total?

Print answers 1 and 2 sorted, so that the order is fixed.

**Expected output:**

```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
```

- `&` — intersection (in both). `-` — difference (only in the first). `|` — union (in at least one of them).
- A set has no fixed order, so `sorted` returns a sorted list for printing.
{{< /solution >}}

### Exercise 5: list, tuple or set?

For each case — which structure would you use? Explain.

1. The coordinates of a point on a map: latitude and longitude.
2. A list of tasks that you add to and remove from, in order.
3. The email addresses of everyone who registered — without duplicates.
4. The checkout queue in a supermarket.

{{< solution >}}
1. **tuple** — a fixed record of two values that are not supposed to change: `(32.08, 34.78)`.
2. **list** — order matters, and you add and remove.
3. **set** — no duplicates, and a fast check whether an address already exists.
4. **list** — order matters, and you take from the start (`pop(0)`).
{{< /solution >}}

<!-- exercises:end -->

## Summary

- **tuple** `( , )` — ordered, **cannot be changed**. Suits a record with a fixed structure. One item: `(x,)`.
- **Unpacking**: `a, b = (1, 2)`. That is how you return and receive several values from a function.
- **set** `{ }` — no duplicates, no order, no index. An empty set = `set()`.
- `set(list)` removes duplicates. `in` on a set is very fast.
- Set operations: `|` union, `&` intersection, `-` difference.
- In the backend and databases topics, rows from the Database will come back as a **tuple**.

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

## Official documentation

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

<!-- docs:end -->
