# dict: Key and Value<no value>

## What will we learn?

A **dict** (Dictionary) is a structure that holds **pairs of a key and a value**. Instead of accessing by position (`[0]`), you access by **name** (`["name"]`).

## Why do we need this?

In the tuple `("Dana", 24, "Haifa")` you have to **remember** that position 1 is the age. With 15 fields, that is a recipe for bugs. A dict solves this:

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

dict is perhaps the **most important** structure for the coming weeks:

- **JSON** (tomorrow): the format in which files are saved and information is sent over the network. In Python it becomes a dict.
- **The backend and databases topics**: every request and response in an API is essentially a dict. `{"id": 7, "name": "Dana"}`.
- Every "record", like a user, a product or an order, is convenient to represent as a dict.

## Explanation from the basics

### What is a dict?

- Every item is a **pair**: a **Key** → a **Value**.
- **Unique keys** — there are no two identical keys. Assigning to an existing key **replaces** the value.
- **Mutable**.
- **Keeps the insertion order** (since Python 3.7).
- A key is usually a `str` or an `int`. A value can be **anything**: a number, text, a list, and even another dict.

{{< box "analogy" "Analogy: a dictionary / a phone book" >}}
In a real dictionary you look up a **word** (the key) and get a **definition** (the value). You don't look for "the definition at position 5,000 in the book". In a phone book: name → number. Two people cannot appear with exactly the same name as separate entries. A new entry with the same name replaces the previous one.
{{< /box >}}

## Syntax

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

## First code example

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

**Output:**

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

## Breaking down the code line by line

| Line | Explanation |
|---|---|
| `{"name": "Dana", ...}` | A key, a colon, a value. Pairs are separated by commas. |
| `student["name"]` | Reading by key: the value `"Dana"`. |
| `student["age"] = 25` | The key **exists**, so the value is **updated**. |
| `student["email"] = "..."` | The key **does not exist**, so it is **added** (at the end). Exactly the same syntax! |
| `del student["city"]` | Deleting the whole pair. |
| `"email" in student` | Checks whether the **key** exists. |
| `student.get("phone", "not provided")` | The key does not exist, so the default is returned, without an error. |

## Another example

**A list of dictionaries** — the most common structure for "real" data (a table: every dict is a row). And also **counting** with a 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")
```

**Output:**

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

**Breakdown:**

- `products` is a **list** where every item is a **dict**. `product["price"]` = the price of the current product in the loop.
- **The counting pattern:** `count_by_category.get(category, 0) + 1`. If the category is not in the dictionary yet, `get` returns 0, and then 0+1 = 1. If it is already there, you get the existing count and add 1. This is a pattern that comes up again and again.
- `.items()` — returns `(key, value)` pairs as tuples, and `for category, count in` unpacks them (Unpacking from the previous lesson).

### A dict inside a dict

A value can be a dict itself:

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

You access it "layer by layer": `user["address"]` returns a dict, and on it `["city"]`. That is exactly the structure of JSON.

## Common mistake

**Mistake 1: a `KeyError` on a key that does not exist.**

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

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

The fix: `student.get("age")` (returns `None`), `student.get("age", 0)` (a default), or check first with `if "age" in student:`.

**Mistake 2: searching for a value with `in`.**

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

```text
False
```

`in` on a dict checks **keys**. To check values: `"Dana" in student.values()`.

**Mistake 3: a typo in a key, which creates a new key instead of updating.**

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

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

No error! A new key `"Age"` was simply added, and `"age"` stayed 24. Keys are case-sensitive. It is a silent Logical Error, so keep the key names consistent (`snake_case`).

**Mistake 4: a list as a key.**

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

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

A key must be **Immutable**. If you need a key made of several values, use a tuple: `{(32, 35): "Haifa"}`. (We will explain the connection to Immutable in the next lesson.)

<!-- 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: A student card

Create a dictionary `student` with the name `"Dana"`, the age 24 and the city `"Haifa"`. Then:

1. Update the age to 25.
2. Add an email `"dana@mail.com"`.
3. Delete the city.
4. Print the dictionary, and the phone using `get` with the default `"none"`.

**Expected output:**

```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"]` would throw a `KeyError`. `get` returns a default value when the key does not exist.
{{< /solution >}}

### Exercise 2: Counting words

Write a function `count_words(text)` that returns a dictionary: every word (in lowercase) → how many times it appears.

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

**Expected output:**

```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)` returns 0 for a word that was not counted yet — that way you do not need a separate `if` for a new word.
{{< /solution >}}

### Exercise 3: A list of dictionaries

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

1. Print the names of the products whose price is below 1000.
2. Build a dictionary that counts how many products there are in each category, and print it.

**Expected output:**

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

### Exercise 4: Looping over a dictionary

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

Loop over the dictionary with `items()` and print every item and its quantity. For an item whose quantity is 0, print `out of stock`.

**Expected output:**

```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()` returns pairs of a key and a value, and Unpacking in the loop splits each pair into two variables.
{{< /solution >}}

### Exercise 5: A nested dictionary

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

Print the city, the first hobby, and add a hobby `"music"`. At the end, print the list of hobbies.

**Expected output:**

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

Every `[ ]` goes one level deeper: `user["address"]` is a dictionary, and `["city"]` takes a value out of it. That is exactly the structure of JSON.
{{< /solution >}}

<!-- exercises:end -->

## Summary

- **dict** `{key: value}` — access by **key** and not by position. Keys are unique.
- `d[key] = value` — **adds** if new, **updates** if it exists.
- `d[key]` throws a `KeyError` on a missing key. `d.get(key, default)` does not throw.
- `in` checks **keys**. Looping: `keys()`, `values()`, `items()` (pairs).
- **A list of dictionaries** = a table. A dict inside a dict = JSON.
- The counting pattern: `counts[x] = counts.get(x, 0) + 1`.

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

## Official documentation

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

<!-- docs:end -->
