# Bases de Python - Partie 2 - Structures de contrôle

Les structures de contrôle vous permettent de contrôler l'exécution de votre programme, c'est-à-dire choisir quelles instructions seront exécutées selon quels cas.

## Les conditions

### Si - alors
Les **structures conditionnelles** ou conditions permettent d'exécuter un code différent selon des conditions : 
```python
if condition:
    print("Faire quelque chose")
```

Cette structure signifie :
- **Si** (`if`) la condition est vraie (`True`)
- **Alors** on exécute le bloc d'instructions

```admonish warning title="Important"
Vous remarquerez que l'on va à la ligne après les `:` marquant la fin de la condition, et que l'on ajoute un décalage de la ligne suivante pour entamer le bloc d'instructions.

Cela s'appelle **l'indentation**. En Python, c'est un élément de syntaxe du langage. C'est à dire que votre code sera erroné si vous n'indentez pas votre code correctement, et cela pourra déclencher des erreurs.
```

### Si - alors - sinon
```python
if condition:
    print("Faire quelque chose")
else:
    print("Faire autre chose")
```

Cette structure signifie:
- **Si** (`if`) la condition est vraie (`True`)
- **Alors** on exécute le bloc d'instructions
- **Sinon** (`else`) on exécute l'autre bloc d'instructions

Remarquez qu'il y a toujours une indentation pour marquer le bloc. En revanche, on revient au niveau précédent pour le `else` car on sort du bloc d'instructions sous le `if`

### Si - alors - sinon, si - sinon

```python
if condition:
    print("Faire quelque chose")
elif autre_condition: 
    print("Faire autre chose")
else:
    print("Faire encore autre chose")
```

Cette structure signifie:
- **Si** (`if`) la condition est vraie (`True`)
- **Alors** on exécute le bloc d'instructions
- **Sinon, si** (`elif` contraction de else if) l'autre condition est vraie
- **Alors** On exécute l'autre bloc
- **Sinon** (`else`) on exécute le dernier bloc d'instructions

```admonish warning title="Attention"
Ne pas oublier le symbole `:` à la fin de chaque ligne commençant par `if`, `elif` ou `else`
```

```admonish tip title='Astuce'
Il est possible d'imbriquer plusieurs conditions, en indentant correctement votre code de la manière suivante:
~~~python
if condition:
   if sous_condition:
        # Faire qqch
    elif sous_condition_2 :
        # Faire autre chose
else:
    # Traiter les autres cas
~~~
```

### Un exemple complet

Nous allons ici écrire un exemple complet avec un code comportant plusieurs conditions complexes. Nous voulons Savoir si une année est bissextile.

Une année est bissextile si c'est un multiple de 4, sauf si c'est un multiple de 100. Mais si c'est un multiple de 400 alors elle est quand même bissextile.

Si on pose les choses de manière structurée :
- Si une année n'est pas un multiple de 4, on s'arrête là : elle n'est pas bissextile.
- Si une année est un multiple de 4 alors on regarde si elle est multiple de 100
    - Si oui, on regarde si c'est un multiple de 400
        - Si oui elle est bissextile
        - Sinon elle ne l'est pas
    - Si elle n'est pas un multiple de 100, elle est bissextile

Prenez le temps de bien réfléchir aux termes du problème et les comprendre. **Essayez de le résoudre par vous-même dans un premier temps.**

Nous allons procéder par étapes :
1. Nous allons demander à l'utilisateur de saisir une année. Si vous avez des doutes, revenez à la partie 1 de ce cours.
    ```python
    annee = int(input("Veuillez saisir une année : ")) # On convertit bien notre entrée en int pour pouvoir faire des calculs dessus
    ```
2. Nous allons gérer la première condition, la plus simple : une année est bissextile si c'est un multiple de 4. Un nombre est multiple d'un autre si l'opération `nombre_1 % nombre_2` donne `0`. L'opérateur `%` est l'opérateur modulo (voir partie 1) et permet de savoir le reste de la division entière de deux nombres. 
    - Nous utilisons donc `annee % 4` pour voir quel est le reste de la division entière par 4. Si c'est zéro, notre année est divisible par 4.
    - Nous utilisons l'opérateur de comparaison `==` pour savoir si notre résultat vaut 0
    ```python
    annee = int(input("Veuillez saisir une année : "))
    if annee % 4 == 0: # Si annee est multiple de 4
        print("Année bissextile") # Alors elle est bissextile
    else: # Sinon
        print("Année non-bissextile") # Elle ne l'est pas
    ```
3. On veut maintenant calculer le cas où notre nombre est un multiple de 400 mais pas un multiple de 100. Nous allons rajouter une condition sous la première (dans le cas où notre nombre est multiple de 4). Pour bien comprendre toutes les conditions, n'hésitez pas à revoir les opérateurs booléens, en partie 1.
    ```python
    annee = int(input("Veuillez saisir une année : "))
    if annee % 4 == 0:
        if (annee % 100 == 0) and not (annee % 400 == 0): # Si annee est multiple de 100 et qu'elle n'est pas (and not) multiple de 400
            print("Année non-bissextile") # Alors elle n'est pas bissextile
        else: # Sinon
            print("Année bissextile") # Alors elle est bissextile
    else: # Sinon
        print("Année non-bissextile") # Elle ne l'est pas
    ```

## Les boucles

Les boucles servent à répéter des instructions autant de fois que nécessaire. Il en existe deux types.
- La boucle `while` (tant que) quand on ne sait pas exactement combien d'itérations on va devoir faire
- La boucle `for` (pour tous les éléments) quand on sait combien de fois on veut répéter, ou que l'on parcourt un ensemble de données

En pratique, en Python on cherche la plupart du temps à utiliser une boucle `for`, mais les deux peuvent être utilisées et doivent être connues.

### Boucle `while`
```python
while condition_est_respectee :
    print("Je fais quelque chose")
```

Cette structure peut se lire de la manière suivante :
- **Tant que** la condition est vraie, 
- **Faire** : on exécute le bloc d'instructions

Dès que la condition n'est plus respectée la boucle se termine. 
- Si la condition n'est jamais respectée, le code de la boucle n'est jamais exécuté. 
- Si la condition est toujours respectée, la boucle s'exécute à l'infini. Cela peut être voulu, ou peut être une source de bugs.
- Il faut donc prévoir, dans votre boucle, un moyen de faire évoluer la validité de la condition.

```admonish warning title="Attention"
- Attention à l'indentation : comme avec les conditions, le bloc de code est décalé par rapport à la ligne précédente pour indiquer le bloc de code à exécuter.
- Attention au `:` comme pour les conditions
- La boucle `do-while` présente en C++ n'existe pas en Python.
```

```admonish example title="Exemple"
On souhaite itérer sur les nombres de 0 à 10 pour afficher une table de multiplication. 

~~~python
i = 0 # On initialise notre variable i à 0
while i <= 10: # Tant que i est inférieur ou égal à zéro
    print(f"{i} * 7 = {i*7}") # On affiche la ligne suivante
    i = i+1 # Et on augment i pour la prochaine itération
~~~
Notre code nécessite de gérer une variable `i` que l'on va augmenter régulièrement pour afficher nos lignes
```

### Boucle `for`

La boucle for est très intéressante. Elle permet de parcourir un ensemble, et d'exécuter du code pour chaque élément de l'ensemble.

```python
for variable in ensemble:
    print(variable)
```

Elle peut se lire de la manière suivante : 
- **Pour tous** les *éléments* dans *l'ensemble*, 
- **Faire** : on exécute le bloc d'instructions


La boucle `for` va **itérer** sur un ensemble de données. Itérer signifie répéter sur chaque élément de l'ensemble en passant au suivant, dans l'ordre.
- Chose intéressante, la boucle `for` met à disposition dans une variable l'élément sur lequel on est en train de travailler.
- L'ensemble peut être une liste, une chaîne de caractères, mais également plein d'autres choses.
- Attention au `:` et à l'indentation

```admonish example title="Exemple"
On souhaite itérer sur les nombres de 0 à 10 pour afficher une table de multiplication. 
~~~python
for i in range(0, 11):
    print(f"{i} * 7 = {i*7}")
~~~

Notre variable `i` vaudra tour à tour toutes les valeurs de 0 à 10, grâce à la fonction `range` qui met à notre disposition tous les nombres de 0 à 10 dans l'ordre. 

Nous n'avons pas besoin de l'initialiser ni de l'incrémenter (=l'augmenter de 1), c'est la boucle `for` qui s'en charge, en passant à l'élément suivant de l'ensemble que nous donne `range`
```

### Stopper une boucle

Pour stopper une boucle, on peut utiliser l'instruction `break`. Cette instruction fonctionne avec les boucles `for` et les boucles `while`.

```admonish example title="Exemple"
On souhaite sortir de la boucle lorsqu'on a trouvé un caractère espace dans la chaîne de caractères et afficher sa position.

~~~python
i = 0                        # On crée une variable pour compter
for char in "Hello, World!": # Pour tous les caractères dans la chaine "Hello, World!"
    i = i+1                  # On incrémente la variable de comptage
    if char == " ":          # On teste si le caractère est un espace
        break                # Si oui, on sort de la boucle
print(f"Il y a un espace en {i}eme position")
~~~
Resultat : `Il y a un espace en 7eme position`
```

### Passer à l'itération suivante

Pour forcer une boucle à passer à l'itération suivante, on peut utiliser l'instruction `continue`. Cette instruction fonctionne avec les boucles `for` et les boucles `while`.

```admonish example title="Exemple"
On voudrait n'afficher que les consonnes et les caractères spéciaux d'une chaîne de caractères

~~~python
for char in "Hello, World!": # Pour tous les caractères dans la chaine "Hello, World!"
    if char in "aeiouy":     # On teste si le caractère est une voyelle
        continue             # Si oui, on reboucle avant d'afficher
    print(char, end="")      # Si ce n'est pas une voyelle, on n'a pas utilisé continue, donc on affiche le caractère
                             # Le paramètre end de print ici sert à éviter de passer à la ligne à chaque print
~~~
Résultat : `Hll, Wrld!`
```