# dict : clé et valeur<no value>

## Qu'allons-nous apprendre ?

Un **dict** (Dictionary, dictionnaire) est une structure qui contient des **paires clé-valeur**. Au lieu d'accéder par position (`[0]`), on accède par **nom** (`["name"]`).

## Pourquoi en avons-nous besoin ?

Dans le tuple `("Dana", 24, "Haifa")`, il faut **se souvenir** que la position 1 est l'âge. Avec 15 champs, c'est la recette des bugs. Un dict résout ce problème :

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

dict est peut-être la structure **la plus importante** pour les semaines à venir :

- **JSON** (demain) : le format dans lequel on enregistre des fichiers et on envoie des informations sur le réseau. En Python, il devient un dict.
- **Les thèmes backend et bases de données** : chaque requête et chaque réponse d'une API est essentiellement un dict. `{"id": 7, "name": "Dana"}`.
- Tout « enregistrement », comme un utilisateur, un produit ou une commande, se représente facilement par un dict.

## Explication à partir des bases

### Qu'est-ce qu'un dict ?

- Chaque élément est une **paire** : une **clé** (Key) → une **valeur** (Value).
- **Des clés uniques** — il n'y a pas deux clés identiques. Affecter à une clé existante **remplace** la valeur.
- **Modifiable**.
- **Conserve l'ordre d'insertion** (depuis Python 3.7).
- Une clé est généralement un `str` ou un `int`. Une valeur peut être **n'importe quoi** : un nombre, du texte, une liste, et même un autre dict.

{{< box "analogy" "Analogie : un dictionnaire / un annuaire" >}}
Dans un vrai dictionnaire, on cherche un **mot** (la clé) et on obtient une **définition** (la valeur). On ne cherche pas « la définition à la position 5 000 du livre ». Dans un annuaire : nom → numéro. Deux personnes ne peuvent pas apparaître avec exactement le même nom en tant qu'entrées séparées. Une nouvelle entrée avec le même nom remplace la précédente.
{{< /box >}}

## Syntaxe

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

## Premier exemple de code

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

**Sortie :**

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

## Analyse du code ligne par ligne

| Ligne | Explication |
|---|---|
| `{"name": "Dana", ...}` | Une clé, deux-points, une valeur. Les paires sont séparées par des virgules. |
| `student["name"]` | Lecture par clé : la valeur `"Dana"`. |
| `student["age"] = 25` | La clé **existe**, donc la valeur est **mise à jour**. |
| `student["email"] = "..."` | La clé **n'existe pas**, donc elle est **ajoutée** (à la fin). Exactement la même syntaxe ! |
| `del student["city"]` | Suppression de toute la paire. |
| `"email" in student` | Vérifie si la **clé** existe. |
| `student.get("phone", "not provided")` | La clé n'existe pas, donc la valeur par défaut est renvoyée, sans erreur. |

## Un autre exemple

**Une liste de dictionnaires** — la structure la plus courante pour des données « réelles » (un tableau : chaque dict est une ligne). Et aussi le **comptage** avec un 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")
```

**Sortie :**

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

**Analyse :**

- `products` est une **liste** dont chaque élément est un **dict**. `product["price"]` = le prix du produit courant dans la boucle.
- **Le modèle de comptage :** `count_by_category.get(category, 0) + 1`. Si la catégorie n'est pas encore dans le dictionnaire, `get` renvoie 0, puis 0+1 = 1. Si elle y est déjà, on obtient le compte existant et on ajoute 1. C'est un modèle qui revient sans cesse.
- `.items()` — renvoie des paires `(key, value)` sous forme de tuples, et `for category, count in` les décompose (l'Unpacking de la leçon précédente).

### Un dict dans un dict

Une valeur peut elle-même être un dict :

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

On y accède « couche par couche » : `user["address"]` renvoie un dict, et sur lui `["city"]`. C'est exactement la structure du JSON.

## Erreur courante

**Erreur 1 : une `KeyError` sur une clé qui n'existe pas.**

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

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

La correction : `student.get("age")` (renvoie `None`), `student.get("age", 0)` (une valeur par défaut), ou vérifier d'abord avec `if "age" in student:`.

**Erreur 2 : chercher une valeur avec `in`.**

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

```text
False
```

`in` sur un dict vérifie les **clés**. Pour vérifier les valeurs : `"Dana" in student.values()`.

**Erreur 3 : une faute de frappe dans une clé, qui crée une nouvelle clé au lieu de mettre à jour.**

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

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

Aucune erreur ! Une nouvelle clé `"Age"` a simplement été ajoutée, et `"age"` est restée à 24. Les clés sont sensibles à la casse. C'est une Logical Error silencieuse, alors gardez des noms de clés cohérents (`snake_case`).

**Erreur 4 : une liste comme clé.**

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

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

Une clé doit être **Immutable**. Si vous avez besoin d'une clé composée de plusieurs valeurs, utilisez un tuple : `{(32, 35): "Haifa"}`. (Nous expliquerons le lien avec Immutable dans la leçon suivante.)

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Exercices

Les exercices n'utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d'abord seul, et n'ouvrez la solution qu'ensuite.

### Exercice 1: Une fiche d'étudiant

Créez un dictionnaire `student` avec le nom `"Dana"`, l'âge 24 et la ville `"Haifa"`. Ensuite :

1. Mettez à jour l'âge à 25.
2. Ajoutez un e-mail `"dana@mail.com"`.
3. Supprimez la ville.
4. Affichez le dictionnaire, et le téléphone avec `get` et la valeur par défaut `"none"`.

**Résultat attendu :**

```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"]` lancerait une `KeyError`. `get` renvoie une valeur par défaut quand la clé n'existe pas.
{{< /solution >}}

### Exercice 2: Compter les mots

Écrivez une fonction `count_words(text)` qui renvoie un dictionnaire : chaque mot (en minuscules) → combien de fois il apparaît.

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

**Résultat attendu :**

```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)` renvoie 0 pour un mot pas encore compté — ainsi, pas besoin d'un `if` séparé pour un nouveau mot.
{{< /solution >}}

### Exercice 3: Une liste de dictionnaires

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

1. Affichez les noms des produits dont le prix est inférieur à 1000.
2. Construisez un dictionnaire qui compte combien de produits il y a dans chaque catégorie, et affichez-le.

**Résultat attendu :**

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

### Exercice 4: Parcourir un dictionnaire

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

Parcourez le dictionnaire avec `items()` et affichez chaque article et sa quantité. Pour un article dont la quantité vaut 0, affichez `out of stock`.

**Résultat attendu :**

```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()` renvoie des paires clé-valeur, et l'Unpacking dans la boucle découpe chaque paire en deux variables.
{{< /solution >}}

### Exercice 5: Un dictionnaire imbriqué

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

Affichez la ville, le premier loisir, et ajoutez un loisir `"music"`. À la fin, affichez la liste des loisirs.

**Résultat attendu :**

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

Chaque `[ ]` descend d'un niveau : `user["address"]` est un dictionnaire, et `["city"]` en extrait une valeur. C'est exactement la structure du JSON.
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **dict** `{key: value}` — accès par **clé** et non par position. Les clés sont uniques.
- `d[key] = value` — **ajoute** si elle est nouvelle, **met à jour** si elle existe.
- `d[key]` lance une `KeyError` sur une clé manquante. `d.get(key, default)` ne lance rien.
- `in` vérifie les **clés**. Parcourir : `keys()`, `values()`, `items()` (paires).
- **Une liste de dictionnaires** = un tableau. Un dict dans un dict = du JSON.
- Le modèle de comptage : `counts[x] = counts.get(x, 0) + 1`.

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

## Documentation officielle

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