Informatique - Support d'enseignement 1re année de licence, Univ. Lille

Philippe Marquet, Maude Pupin

► To cite this version:

Philippe Marquet, Maude Pupin. Informatique - Support d'enseignement 1re année de licence, Univ. Lille. Licence. Informatique, Université de Lille, Villeneuve d'Ascq, France. 2022, pp.211. hal-04131704

HAL Id: hal-04131704

https://hal.science/hal-04131704

Submitted on 16 Jun 2023

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_1.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
Informatique — Support d'enseignement<fnref n="1" />

1re année de licence, Univ. Lille

Équipe pédagogique, Formation en informatique de Lille
Faculté des sciences et technologies, Université de Lille

septembre-décembre 2020

<footnote n="1">¹Philippe Marquet, Maude Pupin, avec le soutien de Laurent Noé, Pierre Allegraud, Patrice Thibaud, inspiré·es des riches versions antérieures de l'Équipe pédagogique.</footnote>

<footnote>Document mis à disposition selon les termes de la licence CC BY — Attribution 4.0 International.</footnote>

<footnote>Contact : philippe.marquet@univ-lille.fr</footnote>
Ce support d'enseignement a été rédigé entre septembre et décembre 2022 à l'occasion de la refonte de l'enseignement d'Informatique de 1re année de licence proposé dans les portails SESI – Sciences exactes et sciences de l'ingénierie –, MIASHS – Mathématiques et informatique appliquées aux sciences humaines et sociales –, et PEIP – Parcours des écoles d'ingénieurs Polytech – de la Faculté des sciences et technologies à l'Université de Lille.

Bien entendu la rédaction de ces supports d'enseignement s'est appuyée sur les riches versions antérieures produites par les équipes pédagogiques successives du département, FIL – Formations en informatique de Lille – que nous remercions chaleureusement.

Ont participé à la rédaction de ce document : Philippe Marquet, Maude Pupin, avec le soutien de Laurent Noé, Pierre Allegraud, et Patrice Thibaud.

<figure><img src="image_2.png" /></figure>

Ce document est mis à disposition selon les termes de la Licence Creative Commons Attribution 4.0 International (CC BY 4.0), creativecommons.org/licenses/by/4.0/deed.fr
Table des matières

<table><tbody><tr><td>1. Valeurs, types et variables — Expressions, instructions</td><td>4</td></tr><tr><td>Valeur et type — chaînes de caractères</td><td>5</td></tr><tr><td>Variables</td><td>8</td></tr><tr><td>Nombres entiers</td><td>11</td></tr><tr><td>Nombres à virgule flottante</td><td>13</td></tr><tr><td>Expressions et instructions</td><td>16</td></tr><tr><td>Exercices niveau débutant</td><td>19</td></tr><tr><td>Exercices niveau intermédiaire</td><td>21</td></tr><tr><td>2. Fonctions</td><td>23</td></tr><tr><td>Utilisation de fonctions</td><td>24</td></tr><tr><td>Bibliothèque et importation de fonctions</td><td>27</td></tr><tr><td>Définition de fonction</td><td>29</td></tr><tr><td>Test de fonction</td><td>33</td></tr><tr><td>Test de fonction avec L1test</td><td>37</td></tr><tr><td>Exercices niveau débutant</td><td>42</td></tr><tr><td>Exercices niveau intermédiaire</td><td>44</td></tr><tr><td>Exercices niveau confirmé</td><td>47</td></tr><tr><td>3. Type booléen - Expressions booléennes - Prédicats.</td><td>50</td></tr><tr><td>Expressions booléennes</td><td>51</td></tr><tr><td>Opérateurs logiques.</td><td>56</td></tr><tr><td>Expressions booléennes et doctests</td><td>60</td></tr><tr><td>Expressions booléennes et L1test.</td><td>63</td></tr><tr><td>Exercices niveau débutant</td><td>67</td></tr><tr><td>Exercices niveau intermédiaire.</td><td>69</td></tr><tr><td>Exercices niveau confirmé</td><td>72</td></tr><tr><td>4. Instruction conditionnelle</td><td>75</td></tr><tr><td>Instruction conditionnelle</td><td>76</td></tr><tr><td>Instruction conditionnelle et alternative</td><td>79</td></tr><tr><td>Instruction conditionnelle - Alternatives multiples.</td><td>82</td></tr><tr><td>Exercices niveau débutant</td><td>85</td></tr><tr><td>Exercices niveau intermédiaire.</td><td>88</td></tr><tr><td>Exercices niveau confirmé</td><td>93</td></tr><tr><td>5. Interaction avec l'utilisateur</td><td>96</td></tr><tr><td>Interaction avec l'utilisateur — Programme</td><td>97</td></tr><tr><td>Écrire sur le terminal</td><td>99</td></tr><tr><td>Lire depuis le terminal.</td><td>102</td></tr><tr><td>Exercices niveau débutant</td><td>105</td></tr><tr><td>Exercices niveau intermédiaire.</td><td>108</td></tr><tr><td>6. Boucles - for.</td><td>112</td></tr><tr><td>Itérable et boucle 'for'.</td><td>113</td></tr><tr><td>Intervalle 'range'.</td><td>116</td></tr><tr><td>Utilisations classiques de boucles 'for'</td><td>118</td></tr><tr><td>Boucles 'for' imbriquées</td><td>122</td></tr><tr><td>Exercices niveau débutant</td><td>124</td></tr><tr><td>Exercices niveau intermédiaire.</td><td>128</td></tr><tr><td>Exercices niveau confirmé</td><td>130</td></tr><tr><td>Exercices complémentaires</td><td>132</td></tr></tbody></table>
<table>
  <tbody>
    <tr>
      <td>7. Chaînes de caractères</td>
      <td>137</td>
    </tr>
    <tr>
      <td>Rappels sur les chaînes de caractères</td>
      <td>138</td>
    </tr>
    <tr>
      <td>Indices pour les chaînes de caractères</td>
      <td>140</td>
    </tr>
    <tr>
      <td>Codage des caractères et comparaison des chaînes de caractères.</td>
      <td>143</td>
    </tr>
    <tr>
      <td>Non mutabilité des chaînes de caractères</td>
      <td>147</td>
    </tr>
    <tr>
      <td>Exercices niveau débutant</td>
      <td>151</td>
    </tr>
    <tr>
      <td>Exercices niveau intermédiaire.</td>
      <td>153</td>
    </tr>
    <tr>
      <td>Exercices niveau confirmé</td>
      <td>156</td>
    </tr>
    <tr>
      <td>8. Itération conditionnelle - while</td>
      <td>158</td>
    </tr>
    <tr>
      <td>Itération conditionnelle</td>
      <td>159</td>
    </tr>
    <tr>
      <td>Utilisations typiques de boucles 'while'</td>
      <td>163</td>
    </tr>
    <tr>
      <td>Autres utilisations typiques de boucles 'while'</td>
      <td>167</td>
    </tr>
    <tr>
      <td>Exercices niveau débutant</td>
      <td>170</td>
    </tr>
    <tr>
      <td>Exercices niveau intermédiaire.</td>
      <td>173</td>
    </tr>
    <tr>
      <td>Exercices niveau confirmé</td>
      <td>177</td>
    </tr>
    <tr>
      <td>9. Listes Python - 1re partie</td>
      <td>178</td>
    </tr>
    <tr>
      <td>Découvrir les listes Python</td>
      <td>179</td>
    </tr>
    <tr>
      <td>Modifier une liste Python</td>
      <td>183</td>
    </tr>
    <tr>
      <td>Exercices niveau débutant</td>
      <td>185</td>
    </tr>
    <tr>
      <td>Exercices niveau intermédiaire.</td>
      <td>188</td>
    </tr>
    <tr>
      <td>Exercices niveau confirmé</td>
      <td>191</td>
    </tr>
    <tr>
      <td>Exercices complémentaires</td>
      <td>193</td>
    </tr>
    <tr>
      <td>10. Listes Python - 2de partie</td>
      <td>194</td>
    </tr>
    <tr>
      <td>Aliasing de listes, valeurs mutables</td>
      <td>195</td>
    </tr>
    <tr>
      <td>Copier une liste</td>
      <td>200</td>
    </tr>
    <tr>
      <td>Copier ou muter une liste ?</td>
      <td>203</td>
    </tr>
    <tr>
      <td>Exercices niveau débutant</td>
      <td>207</td>
    </tr>
  </tbody>
</table>
# 1. Valeurs, types et variables — Expressions, instructions
# Valeur et type – chaînes de caractères

## Coup d'œil

Premier support de cours. On découvre

* les notions de type et valeur en informatique

* les chaînes de caractères

## Types de valeurs

L'informatique est d'un certain point de la vue la science de la manipulation d'informations.

Les informations sont représentées par des **valeurs**.

En Python, toutes les valeurs possèdent un **type**.

Le type d'une valeur définit :

* l'ensemble des valeurs qui peuvent être prises

* les **opérations** qu'il est possible de lui appliquer.

Les types de base sont :

* les entiers, appelés **int**

* les nombres à virgules flottantes (approximation des réels), appelés **float**

* les booléens, appelés **bool**

* les chaînes de caractères, appelées **str**.

Dans un premier temps, nous allons découvrir les chaînes de caractères.

Puis nous nous intéresserons aux nombres.

Enfin, nous traiterons des booléens lors de prochaines séances.

## Chaîne de caractères

Un **caractère** est un symbole qui peut être écrit :

* une lettre (de l'alphabet latin) : **a**, **b**, etc., **X**, **Y**, **Z**

* une lettre accentuée : **à**, **û**, **É**, etc.

* un chiffre : **0**, **1**, etc.

* un symbole de ponctuation tel le point ., le point-virgule ;, etc.

* une espace

* une lettre d'autres alphabets (non-latin) : **α**, **Φ**, ₩, ...

Une **chaîne de caractères** est une séquence de caractères, c'est-à-dire des caractères qui se suivent les uns derrières les autres.

En anglais, on utilise le terme *string*.

Le nombre de caractères d'une chaîne de caractères n'est pas contraint.

Une chaîne de caractères peut ne contenir aucun caractère : on l'appelle alors **chaîne vide**.
# Valeur littérale d'une chaîne de caractères

En informatique, les **valeurs littérales** sont les manières d'écrire des valeurs.

Pour les chaînes de caractères, on écrit tout simplement la séquence des caractères entre deux délimiteurs. On utilise habituellement l'apostrophe ' comme délimiteur.

On écrit donc par exemple :

```python
>>> 'Noémie programme en Python'
'Noémie programme en Python'
```

ou

```python
>>> '314'
'314'
```

La chaîne vide peut s'écrire :

```python
>>> ''
''
```

On peut aussi utiliser les guillemets "`, en particulier quand la chaîne de caractères comporte une apostrophe :

```python
>>> "Noémie s'amuse avec Python"
"Noémie s'amuse avec Python"
```

## Le type str

On peut interroger l'interpréteur Python sur le type d'une valeur :

```python
>>> type('Noémie programme en Python')
<class 'str'>
>>> type("Noémie s'amuse avec Python")
<class 'str'>
```

Autre exemple avec la chaîne vide :

```python
>>> type('')
<class 'str'>
```

ou pour une chaîne constituée exclusivement de chiffres :

```python
>>> type('314')
<class 'str'>
```

## Opérations sur les chaînes de caractères

Des opérations sont définies sur les valeurs de type chaîne de caractères. Nous en étudions une première ici.

La **concaténation** de deux chaînes de caractères :

* produit une chaîne de caractères

* dont la séquence de caractères est la mise bout à bout des deux chaînes de caractères.

Cette opération est notée +.

On peut donc écrire :

```python
>>> 'Noémie ' + 'programme en Python'
'Noémie programme en Python'
>>> 'Noémie ' + "s'amuse avec Python"
"Noémie s'amuse avec Python"
```

ou encore :

```python
>>> '31' + '4'
'314'
```
Memento

* un type définit l'ensemble des valeurs valides pour ce type

• le type d'une valeur définit l'ensemble des opérations possibles sur ces valeurs

• une valeur littérale est une manière d'écrire des valeurs

• une chaîne de caractères est une séquence de caractères

• on écrit les caractères d'une chaîne entre délimiteurs (apostrophes ou guillemets)

• l'opération de concaténation de deux chaînes de caractères est notée +

• en Python, on peut connaître le type d'une valeur avec type(...)
# Variables

## Coup d'œil

On découvre :

* la notion de variable en informatique

* l'instruction d'affectation

## Variables et affectation

Une variable est un nom qui va permettre de désigner une valeur.

Ce nom sera remplacé par la valeur associée à la variable.

L'opération d'affectation permet de lier une valeur à la variable.

La syntaxe de l'affectation (c'est-à-dire la manière d'écrire une affectation) est illustrée par l'exemple suivant

```python
>>> prenom = 'Noémie'
```

On distingue trois éléments :

* une valeur, ici la chaîne de caractères `Noémie`

* le nom de la variable, ici `prenom`

* un signe = qui sépare le nom de la variable - en partie gauche - de la valeur - en partie droite -

La réalisation de l'affectation se déroule en deux étapes :

1. la valeur de la partie droite est calculée

2. cette valeur devient la (nouvelle) valeur associée à la variable

Suite à l'affectation précédente, nous pouvons observer cette association :

```python
>>> prenom
'Noémie'
>>> prenom + ' programme en Python'
'Noémie programme en Python'
>>> "Tu t'appelles " + prenom
"Tu t'appelles Noémie"
```

Il est possible de procéder à une nouvelle affectation d'une variable déjà définie. L'association avec l'ancienne valeur est alors perdue.

Par exemple

```python
>>> prenom
'Noémie'
>>> prenom = 'Ernest'
>>> prenom
'Ernest'
```

La valeur utilisée dans l'affectation peut être le résultat d'une opération (plus généralement d'une expression - nous traiterons des expressions sous peu -) :

```python
>>> elle = 'Noémie'
>>> lui = 'Ernest'
>>> prenoms = elle + lui
```
>>> prenoms
'Noémie Ernest '

Cette valeur partie droite peut même utiliser l'ancienne valeur de la variable qui sera affectée :

>>> prenoms = ''
>>> prenoms
''
>>> prenoms = prenoms + 'Ernest '
>>> prenoms
'Ernest '
>>> prenoms = prenoms + 'Noémie '
>>> prenoms
'Ernest Noémie '

(Remarquons que le signe = utilisé pour l'affectation n'a rien à voir avec le = mathématique ! Que pourrait
signifier prenoms = prenoms + 'Noémie' en maths ?)

Identificateur

Les **identificateurs** sont les noms choisis par le programmeur, par exemple pour nommer ses variables. Ils doivent respecter des règles définies par le langage Python. Ils doivent aussi être choisis avec soin.

En Python,

1. un identificateur ne peut contenir que des lettres, des chiffres ou le blanc souligné _

2. un identificateur ne peut pas commencer par un chiffre (et doit donc commencer par une lettre ou par _)

3. un identificateur ne peut pas être un des mots-clés du langage Python. Ces mots-clés réservés sont les suivants :

<table>
  <tr>
    <td>and</td>
    <td>as</td>
    <td>assert</td>
    <td>break</td>
    <td>class</td>
    <td>continue</td>
    <td>def</td>
  </tr>
  <tr>
    <td>del</td>
    <td>elif</td>
    <td>else</td>
    <td>except</td>
    <td>False</td>
    <td>finally</td>
    <td>for</td>
  </tr>
  <tr>
    <td>from</td>
    <td>global</td>
    <td>if</td>
    <td>import</td>
    <td>in</td>
    <td>is</td>
    <td>lambda</td>
  </tr>
  <tr>
    <td>None</td>
    <td>nonlocal</td>
    <td>not</td>
    <td>or</td>
    <td>pass</td>
    <td>raise</td>
    <td>return</td>
  </tr>
  <tr>
    <td>True</td>
    <td>try</td>
    <td>while</td>
    <td>with</td>
    <td>yield</td>
    <td></td>
    <td></td>
  </tr>
</table>

On évitera les caractères spéciaux et lettres accentuées.

Des identificateurs *significatifs* seront choisis.

En particulier, l'objectif est que ces identificateurs soient immédiatement compréhensibles par le lecteur. On préférera donc par exemple prenoms à p ou prn .

Les identificateurs sont sensibles à la casse, ainsi ide et Ide sont deux identificateurs différents.

On adoptera un nommage cohérent tout au long du semetre, par exemple de

• nommer les variables avec des lettres minuscules et le blanc souligné, par exemple les_prenoms

* nommer les « constantes » avec des lettres majuscules et le blanc souligné, par exemple NOMBRE_MAX
  (une constante est une variable dont la valeur ne sera pas modifiée)

D'autres conventions ou règles d'usages seront introduites plus tard dans le cours.

Mémoire

Lorsque qu'un programme Python est exécuté, les identificateurs et les valeurs associées sont sauvegardées dans
un espace mémoire. Ces associations évoluent lors de l'exécution du programme, on parle de l'état de la mémoire
à un instant donné. La valeur d'une variable, c'est-à-dire son état, est consultable en tapant l'identificateur dans
le shell.

Par exemple :

>>> prenom = 'Noémie'
>>> prenom
'Noémie'
Memento

* une variable est un nom qui désigne une valeur

* l'affectation associe une valeur à une variable

* l'état de la mémoire représente l'ensemble des associations identificateur/valeur d'un programme
# Nombres entiers

## Coup d'œil

On découvre

* le type `int` utilisé pour représenter les nombres entiers

## Le type `int`

Le type `entier` ou `int` de Python permet de représenter n'importe quel nombre relatif.

Les littéraux `entiers` s'écrivent naturellement avec les dix chiffres `0`, `1`, ..., `9` (et donc en base 10).

Par exemple le nombre `42` s'écrira avec la suite de caractères `42`.

```
>>> 42
42
```

Le caractère `-` est utilisé comme préfixe pour les nombres négatifs. Par exemple :

```
>>> -14
-14
```

Il y a cependant quelques règles à respecter :

1. Il ne faut pas utiliser de séparateurs de milliers (ni espace, ni point) : le nombre `10000000` s'écrit en Python `10000000` et non `10\000\000` ou `10\000\000`.

2. Il ne faut pas précéder le premier chiffre d'un `0`, sauf pour le nombre `0`. On doit écrire `7` et non `007`.

3. D'autres règles existent. Elles ne sont pas indispensables pour pouvoir écrire en Python n'importe quel nombre entier.

D'autres écritures (en binaire, octal ou hexadécimal) existent.

## Opérations

Les opérations arithmétiques habituelles sont disponibles sur les valeurs du type `int` :

* l'addition `a + b`,

* la soustraction `a - b`,

* la multiplication `a * b`, et

* l'élévation à la puissance `a ** b`.

Ces opérations produisent une valeur du type `int`.

```
>>> 3 + 14
17
>>> 3 - 14
-11
>>> 3 * 14
42
>>> 2**3
8
```
Il est possible de mettre aucune, une ou plusieurs espaces entre un opérande et l'opérateur. Utilisez ce qui vous semble le plus lisible.

Notons que l'opérateur de multiplication est le `*`, et non le `×`, le point, ou la simple juxtaposition. On ne peut écrire `3 × x`, ni `3.x`, ni `3x`.

La division euclidienne est également disponible.

Prenons par exemple la division euclidienne de 51 par 10. On note $51 = 10 \times 5 + 1$ où 5 est appelé le quotient et 1 le reste.

Ce reste est également nommé modulo.

Le schéma ci-dessous illustre cette division posée comme cela fait pour la résoudre à la main.

<figure><img src="image_3.png" /><figcaption>Figure 1: Exemple d'une division euclidienne posée</figcaption></figure>

* le quotient est obtenu par $a \div b$ (notez bien l'opérateur `//` avec deux barres obliques)

* le reste ou modulo est obtenu par $a \% b$

```python
>>> 51 // 10
5
>>> type(51 // 10)
<class 'int'>
>>> 51 % 10
1
>>> type(51 % 10)
<class 'int'>
```

## Memento

* les valeurs du type `int` représentent les nombres entiers

* on écrit les entiers (en base dix) en respectant quelques règles simples

* les opérations arithmétiques d'addition `+`, soustraction `-`, multiplication `*` et puissance `**` sont disponibles sur les entiers

* le quotient et le reste de la division euclidienne s'obtiennent avec les opérateurs `//` et `%`
# Nombres à virgule flottante

## Coup d'œil

On découvre :

* les *flottants* et le type *float* utilisé pour représenter les nombres réels

* les opérations sur les *flottants*

* comment combiner nombres entiers et nombres flottants

## Le type `float`

Le type *flottants* ou *float* de Python permet de représenter certains nombres réels. On dit aussi *nombre flottant* ou *nombre à virgule flottante*.

Nous pouvons considérer qu'un nombre flottant est la représentation d'une approximation d'un nombre réel, de la même façon que le décimal 3,1416 est une approximation du réel $\pi$.

Notez bien que tous les nombres réels ne peuvent pas être représentés par une valeur de type `float`.

## Littéraux flottants

Deux notations sont possibles pour les littéraux flottants :

* la notation simple,

* la notation scientifique.

### Notation simple

Avec la notation simple, on écrit la partie entière et la partie décimale en les séparant par un point .. Le point est le séparateur décimal habituellement utilisé dans les systèmes anglo-saxons, alors que presque tous les autres systèmes utilisent la virgule.

Comme pour les nombres entiers, on peut préfixer un nombre à virgule flottante par un signe moins -.

>>> 3.1416
3.1416
>>> -1.25
-1.25

L'usage du séparateur décimal . est obligatoire afin de distinguer les flottants des entiers.

>>> 51.
51.0
>>> 51
51
>>> type(51.)
<class 'float'>
>>> type(51)
<class 'int'>

Il est possible d'omettre le zéro de la partie entière :
>>> .5

0.5

## Notation scientifique

La notation scientifique permet de représenter facilement des grands ou petits nombres.

Prenons l'exemple du nombre d'Avogadro, approximé à 6,02214076 × 10²³ mol⁻¹.

On appelle mantisse la partie à gauche du symbole ×, ici 6,02214076. Il s'agit d'un nombre réel.

On appelle exposant la puissance de 10, ici 23. Il s'agit d'un nombre relatif.

Pour la notation scientifique, on écrit successivement :

* la mantisse en notation simple, ici 6.02214076

* le caractère e

* l'exposant, ici 23. On peut aussi écrire +23

Une notation simple du nombre d'Avogadro est 60221407600000000000000.0, longue et peu lisible. On écrira donc plutôt :

>>> 6.02214076e+23

6.02214076e+23

>>> 6.02214076e23

6.02214076e+23

>>> 60221407600000000000000.0

6.02214076e+23

## Opérations flottantes

Les opérations arithmétiques habituelles sont disponibles sur les valeurs du type `float` :

* l'addition `x + y`, la soustraction `x - y`, la multiplication `x * y`, et l'élévation à la puissance `x ** y`.

Ces opérations produisent une valeur du type `float`.

>>> 3.0 + 0.14

3.14

>>> 3.0 - 1.4

1.6

>>> 3.0 * 14.

42.0

>>> 9.0 ** 0.5

3.0

La division flottante est également disponible, notée de la simple barre oblique : `x / y`.

Elle produit une valeur flottante.

>>> 5.0 / 2.0

2.5

>>> 4.0 / 2.0

2.0

>>> 1.1 / 0.1

11.0

## Approximation des nombres réels

Les flottants sont des approximations, certains résultats peuvent paraître surprenants.

>>> 0.1 + 0.2

0.30000000000000004

Le résultat du calcul précédent est précis à 10⁻¹⁷ près. Pour en savoir plus, vous pouvez consulter la documentation Python
# Opérations entre entiers et flottants

L'ensemble des opérations arithmétiques s'appliquent à des nombres entiers et flottants.

Pour toutes les opérations sauf la division flottante, le type du résultat :

* est `int` si les deux opérandes sont des `int`

* est `float` si les deux opérandes sont des `float`

* est `float` si une opérande est un `int` et l'autre un `float`

```
>>> 3 * 14
42
>>> 3.0 * 14.0
42.0
>>> 3.0 * 14
42.0
>>> 3 * 14.0
42.0
```

La division flottante `/` produit toujours une valeur de type `float`, même si les deux opérandes sont de type `int` :

```
>>> 5 // 2
2
>>> 5 / 2
2.5
>>> 4 // 2
2
>>> 4 / 2
2.0
```

## Memento

* les nombres flottants sont des représentations ou approximations des nombres réels

* le type `float` est utilisé pour représenter les nombres flottants

* la notation simple des flottants de la forme `123.45` utilise le point comme séparateur décimal

* la notation scientifique des flottants de la forme `12.34e+56` distingue mantisse et exposant

* les opérations arithmétiques d'addition `+`, soustraction `-`, produit `*`, division euclidienne `//` et `%`, et puissance `**` sont disponibles sur les flottants et les entiers

* la division flottante notée `/` produit toujours une valeur flottante
# Expressions et instructions

## Coup d'œil

On découvre

* comment combiner plusieurs opérations pour former des expressions

* d'autres opérations diverses

On précise

* la notion d'instruction et d'état

## Expressions

Il est possible de combiner plusieurs opérateurs pour former une *expression*.

On écrit par exemple `3 + 4 - 2`.

En programmation, une expression est la notation d'un calcul à réaliser.

Lors de l'exécution, l'expression sera toujours réduite à une valeur.

On dit qu'elle est *évaluée*.

Cette évaluation se fait en suivant un ordre de priorité qui définit l'ordre des opérations à effectuer.

Par exemple, l'évaluation de `3 + 4 - 2`

* calculera la somme de `3 + 4`

* puis fera la différence entre cette valeur et `2`

Le tableau ci-dessous classe les opérateurs par ordre de priorité décroissant. Les opérateurs sur la même ligne ont la même priorité.

<table><thead><tr><th></th><th>**</th><th>//</th><th>%</th></tr></thead><tbody><tr><td>*</td><td>/</td><td>-</td><td></td></tr><tr><td>+</td><td></td><td></td><td></td></tr></tbody></table>

>>> `2 * 3 + 5 * 1`

11

>>> `2 ** 2 * 2`

8

Lorsque deux opérateurs ont la même priorité, ils sont évalués de gauche à droite sauf les `**` qui sont évalués de droite à gauche.

>>> `2 - 3 - 1`

-2

>>> `5 // 2 * 2`

4

>>> `2 ** 2 ** 3`

256

Il est possible de contrôler l'ordre d'évaluation des opérations en utilisant des parenthèses ( et ) :

>>> `2 * (3 + 5) * 1`

16
>>> 2 - (3 - 1)
0
>>> (2 ** 2) ** 3
64

On ne se privera pas d'utiliser les parenthèses.

## Instructions

Une instruction exprime un ordre. Son exécution va modifier l'état de la machine, de l'ordinateur.

L'état est essentiellement l'ensemble des valeurs associées aux variables définies.

La seule instruction que nous connaissons pour le moment est l'affectation. Elle modifie la valeur associée à une variable, donc l'état.

(La position du curseur à l'écran et d'autres choses font aussi parties de l'état.)

Remarquons qu'une expression n'est pas une instruction. Elle ne modifie en effet pas l'état.

Un programme est une suite d'instructions.

L'exécution du programme consiste en l'exécution successive des instructions pour faire passer l'état de l'état initial à un état final.

## Autres opérations

### Répétition de chaînes de caractères

L'opérateur `*` appliqué à une valeur entière `n` et une chaîne de caractères `s` :

* produit une chaîne de caractères

* résultat de `n` concatenations de la chaîne `s`.

>>> triple = 3
>>> triple * 'bravo ! '
'bravo ! bravo ! bravo ! '
>>> 'bravo !' * 3
'bravo !bravo !bravo !'

D'autres opérations sur les chaînes de caractères seront présentées plus tard.

Remarquons que les expressions peuvent être formées d'opérateurs de toute sorte :

>>> double = 2
>>> double * ('bravo,' + ' ') + 'bravo !'
'bravo, bravo, bravo !'

### Conversions

Des fonctions prédéfinies du langage Python permettent de changer le type d'une valeur, lorsque c'est possible.

Ce changement s'appelle une conversion de type.

La fonction prédéfinie `str()` produit une représentation sous forme d'une chaîne de caractères de la valeur passée en paramètre, par exemple d'une valeur numérique.

>>> str(12)
'12'
>>> str(42.14)
'42.14'

Réciproquement, la fonction prédéfinie `int()` produit un nombre entier à partir d'une chaîne de caractères, si et seulement si cette chaîne ne contient que des chiffres, éventuellement précédés du signe `-`. Si un autre caractère est présent, à part les espaces qui sont tolérés, une erreur est affichée.

>>> int('12')
12
>>> int('007')
```python
7
>>> int('-12')
-12
>>> int('12.')
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '12.'
>>> int('12.')
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '12.'
>>> int('23.5')
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '23.5'
>>> int('24°C')
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '24°C'
```

Enfin, la fonction prédéfinie `float()` produit un nombre flottant à partir d'une chaîne de caractères, si et seulement si cette chaîne ne contient que des chiffres, éventuellement un `.` et éventuellement précédés du signe `-`. Si un autre caractère est présent, à part les espaces qui sont tolérés, une erreur est affichée.

```python
>>> float('12')
12.0
>>> float('007')
7.0
>>> float('-12')
-12.0
>>> float('12.')
12.0
>>> float('23.5')
23.5
>>> float('24°C')
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
ValueError: could not convert string to float: '24°C'
```

## Memento

* une expression combine une ou plusieurs opérations

* l'évaluation d'une expression la réduit à une valeur

* des règles de priorités fixent l'ordre d'application des opérations

* les parenthèses permettent de préciser l'ordre d'application des opérations

* une instruction modife l'état de l'ordinateur

* l'affectation est une instruction qui modifie l'association des valeurs aux variables

* des fonctions pré-définie permettent de convertir une valeur d'un type en un autre type, sous certaines conditions.
# Exercices niveau débutant

## Expressions arithmétiques

Pour les questions qui suivent, n'utilisez pas tout de suite l'interpréteur Python (ni une calculatrice).

* Calculez les valeurs des expressions et précisez leur type.

* Vérifiez, ensuite la validité de votre réponse en utilisant l'interpréteur et la fonction `type`

1. Quelques expressions.

```
10 + 2 * 7
(10 + 2) * 7
35. - 5 * 8
-5-5
+5-5
5+-5
5--5
```

2. Indiquez la signification des trois opérateurs `/`, `//` et `%`.

3. Quelle est la différence entre les expressions ci-dessous ? Ont-elles la même valeur ? Le même type ?

```
5 / 2
5 // 2
5 % 2
5 / 2.
5 // 2.
```

## Variables et affectation

4. Sans utiliser l'interpréteur Python, prévoyez si ces noms de variable sont valides ou non.

<table><tr><td>n</td><td>mon_ue</td><td>dame</td></tr><tr><td>test</td><td>mon ue</td><td>roi</td></tr><tr><td>$test</td><td>mon-ue</td><td>as</td></tr><tr><td>test1</td><td>monUe</td><td>avec</td></tr><tr><td>1test</td><td>monUE</td><td>with</td></tr><tr><td>-</td><td>Mon_Ue</td><td>sans<br>without</td></tr></table>

5. Décrivez l'évolution des valeurs associées aux variables au fur et à mesure de l'exécution des instructions suivantes.

Donnez les valeurs finales de chaque variable.

En d'autres termes, décrivez l'état de la mémoire après chaque instruction.

```
a = 10
b = a
a = 12
b = b + 1
```
x = 1

y = 2

z = y

y = x

x = z

6. Affectez aux identificateurs a, b, c et d respectivement les valeurs 1, 2, 3 et 4 puis transcrivez, en expressions arithmétiques Python3, les formules mathématiques suivantes.

• $(a+b)^2$, vous devez trouver 9

• $a^2 + 2ab + b^2$, vous devez trouver 9

• $\frac{a+b}{c+d}$, vous devez trouver environ 0,42857

• $\frac{1}{a+1}$, vous devez trouver 0,5

7. Consultez les modalités de calcul de la note finale de l'UE *Informatique*.
Écrivez les instructions nécessaires à son calcul à l'aide de variables et observez la note finale obtenue avec différents jeux de notes intermédiaires.

Chaînes de caractères

8. Affectez à des variables les chaînes de caractères suivantes :

• C'est l'automne !

• Il fait 18°C.

• Ceci " est un guillemet.

• Ce caractère " n'est-il pas un guillemet ?

9. Calculez les valeurs des expressions suivantes et précisez leur type. Vérifiez ensuite la validité de votre réponse en utilisant l'interpréteur et la fonction *type*.

Comme dans un exercice précédent, n'utilisez pas tout de suite l'interpréteur Python.

'5' + '5'

5 + '5'

'5' - '5'

5 * '5'

'5' * '5'
# Exercices niveau intermédiaire

## Échange de valeurs

10. Soient les deux variables définies suite à l'exécution des instructions suivantes

n1 = 'Un'
n2 = 'Deux'

Quelles sont les nouvelles valeurs associées à ces variables suite à l'exécution de

n2 = n1
n1 = n2

Proposez une méthode pour échanger les valeurs des deux variables : à la fin, n1 doit contenir la valeur 'Deux' et n2 doit contenir 'Un'.

Cette méthode doit bien entendu fonctionner quelles que soient les valeurs des variables.

11. Écrivez maintenant une suite d'instructions pour effectuer une permutation circulaire avec 3, puis 4 variables : la première variable prend la valeur de la dernière, la dernière de la précédente, ainsi de suite. Par exemple pour les trois variables

n1 = 'Un'
n2 = 'Deux'
n3 = 'Trois'

à l'issue de la permutation, n1 sera associée à la valeur 'Trois', n2 à la valeur 'Un', et n3 à la valeur 'Deux'.

## Programme de calcul

12. Donner une suite d'instructions pour effectuer les calculs suivants.

* soit un nombre

* lui ajouter 3

* multiplier par 2

* ajouter le nombre initial

* soustraire 12

* diviser par 3

* ajouter 2

## Calculs d'intérêts

Supposez que vous disposez d'un capital d'un million d'euros (on peut rêver), et que vous l'avez investi dans un placement qui vous rapporte 5% d'intérêt par an. Supposez enfin que ces intérêts sont ajoutés au capital tous les six mois. De quel capital disposerez-vous dans cinq ans ?

La formule pour calculer le montant final du capital est la suivante :

$$A = C\left(1 + \frac{r}{n}\right)^{nt}$$

où
* C est le capital initial

* r est le taux d'intérêt

* t est le nombre d'années

* n est le nombre de fois que les intérêts sont calculés et ajoutés au capital chaque année (par exemple, $n = 2$ dans la situation décrite ci-dessus).

13. Définissez les variables C, r, t et n, puis calculez le montant final dans une variable A_t5_n2 selon la formule ci-dessus.

Vous devez trouver environ 1280084, 54

14. Refaites le calcul en supposant que les intérêts sont capitalisés tous les trimestres et affectez la valeur obtenue à une variable A_t5_n4. Quelle est la différence entre les deux capitaux résultants ?

Vous devez trouver environ 1282037, 23

## Chiffres et divisions euclidiennes

15. Donnez une expression calculant le chiffre des unités de 2031. (Vous devez trouver 1.)

16. Donnez une expression calculant le nombre de dizaines de 2031. (Vous devez trouver 203.)

17. Donnez une expression calculant le chiffre des dizaines de 2031. (Vous devez trouver 3.)

18. Définissez une variable nip contenant votre NIP - numéro étudiant - (avec le type entier), une variable n contenant l'entier 5, puis écrivez une expression utilisant ces deux variables et calculant le n-ième chiffre en partant de la droite de votre NIP.

## Dans l'ordre

19. Soient les instructions Python suivantes :

```python
str1 = 'oh '
str2 = str2 + '!'
str2 = coeff * str1
coeff = 3
```

Mettez ces instructions dans l'ordre pour que suite à leur exécution, la valeur associée à la variable str2 soit « 'oh oh oh !' ».

20. Même chose avec l'ensemble suivant d'instructions :

```python
str1 = 'oh '
str1 = 'oh !'
str2 = str2 + '!'
coeff3 = coeff2 - 1
str2 = coeff2 * str1
coeff2 = 3
```

Vous devez utiliser toutes les instructions.
## 2. Fonctions
# Utilisation de fonctions

## Coup d'œil

On découvre

* la notion de fonction

* comment réaliser un appel de fonction et récupérer la valeur de retour

* quelques fonctions prédéfinies de Python

* comment accéder à la documentation d'une fonction

## Fonction (en informatique)

Fonction ≡ Algorithme

données → [algorithme] → données

Fonction

* prend des données en entrée – ses **paramètres**, appelés aussi arguments
  – ce sont des valeurs

* produit une valeur – son **résultat**
  – on dit aussi *renvoie* un résultat

Spécification ≡ contrat avec l'utilisateur de la fonction.

Spécification

* description de l'objectif, de ce que fait la fonction

* description des attendus sur les paramètres (types, contraintes particulières)

* description de la valeur renvoyée (type, particularités)

Une fonction est un sous-programme qui permet d'organiser son code. Elle est une suite d'instructions qui sert à faire des actions précises. Le but des fonctions est de simplifier un programme pour le rendre plus lisible, pour ne pas avoir à retaper le même code plusieurs fois dans un même programme, pour éviter les erreurs.

## Appel de fonction

Python, comme de nombreux langages, propose des fonctions prédéfinies. Elles sont utilisables directement.

Par exemple, la fonction `len()` renvoie le nombre de caractères d'une chaîne de caractères.

On utilise une fonction via un *appel* : on écrit le nom de la fonction suivi des paramètres entre parenthèses.

Par exemple pour cette fonction `len()`

>>> len('informatique')

12

On obtient bien le nombre de caractères de la chaîne `'informatique'`

L'appel de la fonction est réalisé ainsi :

1. les paramètres sont évalués, c'est-à-dire que les expressions sont réduites à une valeur.
2. le code de la fonction est exécuté.

3. la valeur de l'appel de fonction est la valeur renvoyée par la fonction.

Sur cet autre exemple :

```python
>>> len('info' + 'r' + 'matique')
12
```

1. l'expression qui correspond au paramètre - `'info' + 'r' + 'matique'` - est évaluée. On obtient donc la valeur `'informatique'`.

2. le code qui correspond à la fonction `len()` est exécuté avec comme paramètre cette valeur `'informatique'`.

3. cet appel de fonction produit la valeur `12`.

Bien entendu, cette valeur renvoyée par la fonction peut être affectée à une variable, ou utilisée dans une autre expression.

Par exemple :

```python
>>> nbcar = len('informatique')
>>> nbcar
12
```

ou

```python
>>> d = len('informatique') - len('Python')
>>> d
6
```

Une fonction peut aussi accepter plusieurs paramètres, voir un nombre variable de paramètres. Lors d'un appel à la fonction, ils seront séparés par des virgules.

Par exemple, la fonction prédéfinie `min()` renvoie le plus petit de ses paramètres. On peut donc écrire :

```python
>>> min(12, 6)
6
>>> min(3, 4, 1, 2, 4)
1
```

Une fonction peut n'accepter aucun paramètre. On écrit alors simplement le nom de fonction suivi de parenthèses.

Par exemple, la fonction `dir()` renvoie une liste des variables (pré-)définies :

```python
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'd'
```

Une autre fonction prédéfinie, `str()` que nous avons déjà rencontrée, produit une représentation sous forme d'une chaîne de caractères de la valeur passée en paramètre, par exemple d'une valeur numérique. On a :

```python
>>> str(12)
'12'
>>> str(42.14)
'42.14'
```

En combinant plusieurs appels de fonction, on peut écrire :

```python
>>> 1 + len(str(2 * min(12, 6)))
3
```

→ Donner les différentes étapes de l'exécution de cette instruction.

## Documentation

Les fonctions sont généralement documentées : les programmeurs décrivent brièvement la spécification de la fonction.

Il est possible d'accéder à la documentation à l'aide de la fonction `help()`. Pour obtenir l'aide sur la fonction prédéfinie `abs()`, on écrira :
>>> help(abs)
Help on built-in function abs in module builtins:

abs(x, /)
Return the absolute value of the argument.

Cette aide est interactive si elle tient sur plusieurs pages :

* on passe à la page suivante avec la touche « *espace* »

* on quitte l'aide en utilisant la touche « q »

## Memento

* une fonction met en œuvre un algorithme dont on connaît la spécification

* un appel de fonction correspond à une exécution de la fonction

* on appelle une fonction en fournissant son nom suivi d'une série de valeurs entre parenthèses

* ces valeurs sont les paramètres de l'appel

* une fonction accepte un certain nombre de paramètres, éventuellement aucun

* un appel de fonction renvoie une valeur, dite valeur de retour

* Python propose des fonctions prédéfinies

* les fonctions sont documentées. La fonction prédéfinie `help()` permet de consulter cette documentation
# Bibliothèque et importation de fonctions

## Coup d'œil

On découvre :

* la notion de module ou bibliothèque de fonctions

* comment importer et utiliser les fonctions d'un module

* quelques fonctions de modules couramment utilisés

## Modules Python

Les fonctions prédéfinies sont accessibles directement en Python. D'autres fonctions sont proposées via des **bibliothèques**. On parle aussi de **modules**.

Python fournit de très nombreux modules. Nous en découvrirons quelques-uns au fur et à mesure de ce semestre.

Voyons ci-dessous un bref aperçu de deux modules que nous utiliserons dans nos exemples.

### Le module math

Le module `math` de Python contient les définitions de nombreuses fonctions mathématiques telles que `sinus` – `sin()` –, `cosinus` – `cos()` –, `tangente` – `tan()` –, `racine carrée` – `sqrt()` –, etc.

Il contient également des constantes telles que `π`, noté `pi`.

### Le module random

Le module `random` de Python regroupe les fonctions de hasard (tirage au sort, génération aléatoire). Nous utiliserons les fonctions

`randint()` qui accepte deux paramètres de type entier – bornes inférieure et supérieure d'un intervalle – et renvoie un entier, choisi au hasard dans cet intervalle, bornes comprises.

`choice()` qui accepte par exemple une chaîne de caractères et renvoie une des lettres de la chaîne, choisie au hasard.

D'un appel à l'autre ces fonctions renvoient donc possiblement une valeur différente.

## Importer les fonctions d'un module

L'utilisation d'une fonction d'un module nécessite une importation préalable.

Il est possible :

* d'importer un module

* d'importer une fonction particulière d'un module

* d'importer toutes les fonctions d'un module

L'importation n'est réalisée qu'une fois, habituellement en début de programme ou de session. De multiples appels à la fonction sont ensuite possibles.
## Importer un module

Pour importer une module, par exemple le module `random`, on utilise la syntaxe suivante

```python
import random
```

On identifiera les fonctions importées en préfixant leur nom par celui du module. Par exemple

```python
>>> random.choice('aeiouy')
'u'
```

ou

```python
>>> randomrandint(12, 42)
33
```

## Importer une fonction particulière d'un module

Pour importer une fonction d'un module, par exemple la fonction `choice()` du module `random`, on utilise la syntaxe suivante

```python
from random import choice
```

On identifiera simplement la fonction importée par son nom. Par exemple

```python
>>> choice('aeiouy')
'y'
```

Remarquons que les autres fonctions du module ne sont pas disponibles :

```python
>>> randint(12, 42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'randint' is not defined
```

Le message d'erreur indique que le nom `randint` n'est pas défini.

## Importer toutes les fonctions d'un module

On peut vouloir importer l'ensemble des fonctions d'un module. On utilise alors la syntaxe

```python
from random import *
```

On identifiera simplement les fonctions importées par leur nom. Par exemple

```python
>>> choice('aeiouy')
'o'
```

et

```python
>>> randint(12, 42)
17
```

→ On pourra vérifier que d'autres fonctions du module `random` ont aussi été importées. Par exemple via un appel à `dir()` ou dans le volet « Variables » de notre environnement de programmation Thonny.

On évitera d'utiliser ce moyen d'importation qui « pollue » l'espace de noms : de nombreuses fonctions deviennent disponibles alors que nous n'allons pas les utiliser.

## Memento

* un module ou bibliothèque regroupe un ensemble de fonctions

* il faut importer les fonctions d'un module avant de pouvoir les utiliser

* il existe plusieurs syntaxes pour importer une ou des fonctions

* une fois importée, l'appel à une fonction d'un module se fait sous la forme `fonction()`

* l'appel à une fonction d'un module se fait parfois sous la forme `module.fonction()`
# Définition de fonction

## Coup d'œil

On découvre

* comment créer un bloc d'instructions

* comment définir nos propres fonctions

* pourquoi définir des fonctions

* comment décrire la spécification et la documentation de nos fonctions

## Indenter un bloc de code

Une série d'instructions peut être regroupée en un bloc. Dans de nombreux langages, des mots-clés – `begin` et `end` par exemple –, ou des accolades `{ et }` marquent ces blocs. En Python, on identifie les instructions faisant partie d'un même bloc par le fait qu'elles sont écrites décalées vers la droite à un même niveau. On dit qu'elles sont *indentées*.

Il est possible d'utiliser le nombre d'espaces que l'on veut pour indenter un bloc. Par contre, ce nombre doit toujours être le même au sein d'un programme. Il est cependant conseillé d'utiliser 4 espaces, 8 pour les blocs imbriqués au second niveau, etc.

On n'utilisera pas la tabulation pour l'indentation.

La fin du bloc est marquée par la fin de l'indentation, plus précisément par la reprise de l'indentation précédente.

Sur un exemple

```python
instruction bloc niveau 0
instruction bloc niveau 0
	instruction bloc niveau 1
	instruction bloc niveau 1
		instruction bloc niveau 2
		instruction bloc niveau 2
		instruction bloc niveau 2
		instruction bloc niveau 1
	instruction bloc niveau 0
```

## Notre première fonction

Au delà des fonctions prédéfinies et des fonctions des bibliothèques, il est possible de définir nos propres fonctions. Voyons comment procéder sur un exemple.

La définition d'une fonction est introduite par le mot-clé `def` suivi du nom de la fonction et d'une liste d'identificateurs. Ce sont les paramètres.

Cette première ligne se termine par un `:`. On écrit donc par exemple :

```python
def celsius2fahrenheit(cels):
```

Les lignes suivantes sont le corps de la fonction et constituent un bloc d'instructions. Elles seront indentées d'un niveau.
La première instruction de la fonction peut être une **docstring** – *documentation string* –, la chaîne de caractères documentation de la fonction. Elle est délimitée par de triples guillemets.

Notre fonction débute donc par :

def celsius2fahrenheit(cels):

"""Convertit en Fahrenheit une température en degrés Celsius

Paramètre :

- cels : float, température en degrés Celsius

Valeur de retour :

- float (température en degrés Fahrenheit)

###

Le corps de la fonction lui-même, c'est-à-dire les instructions qui suivent, seront exécutées à chaque appel de la fonction.

Dans ces instructions, les identificateurs des paramètres sont utilisés pour accéder aux valeurs fournies lors de l'appel.

On écrira par exemple

def celsius2fahrenheit(cels):

"""Convertit en Fahrenheit une température en degrés Celsius

Paramètre :

- cels : float, température en degrés Celsius

Valeur de retour :

- float (température en degrés Fahrenheit)

###

fahr = ((9 * cels) / 5) + 32

return fahr

L'instruction `return` met fin à l'exécution de la fonction, quelque-soit sa position dans le bloc d'instruction de la fonction. La valeur fournie à `return` est la valeur qui sera renvoyée par la fonction.

Suite à notre définition de fonction, nous pouvons :

* consulter la documentation de la fonction :

>>> help(celsius2fahrenheit)

Help on function celsius2fahrenheit in module _main_:

celsius2fahrenheit(cels)

Convertit en Fahrenheit une température en degrés Celsius

Paramètre :

- cels : float, température en degrés Celsius

Valeur de retour :

- float (température en degrés Fahrenheit)

* faire des appels à cette fonction :

>>> celsius2fahrenheit(24)

75.2

## Composer des fonctions

Par composition de fonctions on élabore des algorithmes de plus en plus complexes.

Cette démarche repose sur de grands principes élémentaires de programmation :

* décomposition

* réutilisation

* modularité

* encapsulation
Définissons une deuxième fonction :

En montagne, plus on s'élève, plus il fait froid. La température baisse de 0,65°C tous les 100 mètres.

```python
def delta(alt) :
    """Température perdue à une altitude donnée en mètres"""
    return 0.65 * alt / 100
```

Et une troisième qui utilise les deux précédentes :

```python
def fahr_alt(t_ref, alt) :
    """Température en Fahrenheit à une altitude donnée, pour une température
de référence en Celsius
"
```

Paramètres :

- t_ref : température de référence en Celsius

- alt : altitude en mètres

Valeur de retour :

- float (température en Fahrenheit à l'altitude donnée)

```python
"
```

cels = t_ref - delta(alt)

fahr = celsius2fahrenheit(cels)

return fahr

On peut alors calculer la température en Fahrenheit à 3000 m d'altitude étant donné qu'il fait 24°C ici :

```python
>>> temp_ici = 24
```

```python
>>> fahr_alt(temp_ici, 3000)
```

40.1

## Documenter nos fonctions

La documentation de nos fonctions est fournie sous la forme d'une simple chaîne de caractères.

On précisera particulièrement le sens des paramètres, le type attendu pour ces paramètres, et les contraintes d'utilisation, le type de la valeur renvoyée.

La documentation des fonctions définies dans les exemples précédents comporte certains de ces éléments. Nous pouvons la compléter, en particulier en ce qui concerne les contraintes d'utilisation :

Le calcul de la variation de température en fonction de l'altitude tel que décrit précédemment n'est plus valable quand on atteint la stratosphère, disons 12 km. Par ailleurs, une température ne peut être inférieure à -273,15°C.

```python
def celsius2fahrenheit(cels) :
    """Convertit en Fahrenheit une température en degrés Celsius
"
```

Paramètre :

- cels : float, température en degrés Celsius

Valeur de retour :

- float (température en degrés Fahrenheit)

Contrainte :

- la température cels doit être strictement supérieure à -273,15°C
```python
"
```

fahr = ((9 * cels) / 5) + 32

return fahr

```python
def delta(alt) :
    """Température perdue à une altitude donnée en mètres
"
```

Paramètre :

- alt : altitude en mètres

Valeur de retour :

- float (variation de température due à l'altitude)

Contrainte :

- l'altitude alt doit être inférieure à 12 000 m
```python
"""
return 0.65 * alt / 100
```

L'utilisateur des fonctions doit se plier à la documentation, et ne pas faire d'appel à la fonction avec des valeurs de paramètres qui ne respecteraient pas les contraintes.

## Memento

* on utilise l'indentation pour identifier les blocs d'instructions

* le mot-clé `def` introduit la définition d'une fonction

* les paramètres sont des identificateurs qui permettent d'accéder aux valeurs passées à la fonction

* un bloc d'instructions forme le corps de la fonction

* l'instruction `return` met fin à l'exécution de la fonction

* la valeur renvoyée par la fonction est celle associée au `return`

* on compose des fonctions pour en former de plus complexes

* une documentation sous forme de docstring décrit la spécification de la fonction

* cette documentation précise le type des paramètres et de la valeur de retour, et éventuellement des contraintes sur les valeurs attendues
# Test de fonction

## Coup d'œil

On découvre

* comment compléter la spécification et la documentation des fonctions par des exemples et tests

* comment automatiser les tests de nos fonctions

## Une fonction plus_grande_racine()

Nous désirons définir une fonction qui renvoie la plus grande racine d'un polynôme du second degré $ax^2 + bx + c$.

La fonction acceptera trois paramètres : les valeurs des coefficients $a, b$, et $c$.

Cette plus grande racine est évidemment égale à :

$$max(\frac{-b + \sqrt{\Delta}}{2a}, \frac{-b - \sqrt{\Delta}}{2a})$$

avec

$$\Delta = b^2 - 4ac$$

pourvu que $\Delta \ge 0$.

## Prototype et documentation

La première étape est de définir l'interface et la documentation de notre fonction `plus_grande_racine()`. On écrit par exemple :

```python
def plus_grande_racine (a, b, c):
    """Renvoie la plus grande racine du polynôme de second degré ax² + bx + c
```

Paramètres :

- a : (int or float) coefficient a - associé à $x^2$

- b : (int or float) coefficient b - associé à $x$

- c : (int or float) coefficient c - constant

Valeur de retour :

- float (la plus grande racine du polynôme)

"""

pass

L'exécution de l'instruction `pass` ne produit rien. Elle est utilisée pour remplacer du code qui n'est pas encore écrit car elle évite la génération d'une erreur lors de l'exécution d'un programme incomplet.

## Contraintes d'utilisation

L'utilisation de cette fonction `plus_grande_racine()` nécessite le respect de deux contraintes, appelées aussi conditions d'utilisation :
* $b^2 - 4ac \ge 0$

* $a \neq 0$

Il nous faut l'indiquer dans la documentation de la fonction. On écrira donc :

def plus_grande_racine (a, b, c):
"""Renvoie la plus grande racine du polynôme de second degré ax^2 + bx + c

Paramètres :

- a : (int or float) coefficient a - associé à x^2

- b : (int or float) coefficient b - associé à x

- c : (int or float) coefficient c - constant

Valeur de retour :

- float (la plus grande racine du polynôme)

Contraintes :

- a doit être non nul

- (b**2 - 4*a*c) doit être positif ou nul

### pass

## Exemples d'utilisation

La documentation peut aussi être enrichie d'exemples d'utilisation de la fonction.

Si nous avions complètement défini notre fonction, nous pourrions l'utiliser :

>>> plus_grande_racine(1, 1, -6)

2.0

>>> plus_grande_racine(5, 0, -5)

1.0

>>> plus_grande_racine(2, 2, 0)

0.0

Ces codes Python d'appel de la fonction et de résultats attendus permettent

* à un utilisateur de la fonction, d'éventuellement mieux comprendre la spécification de la fonction,

* à un programmeur de la fonction, de s'assurer que le code de sa fonction produit un résultat correct sur ces quelques exemples.

## Tests unitaires

Un jeu de tests est un ensemble de jeux de paramètres et des résultats attendus.

Un test unitaire d'une fonction est la vérification que l'exécution de la fonction sur un jeu de tests produit bien les résultats attendus.

Les jeux de paramètres seront choisis avec soin, par exemple pour inclure les cas limites ou des cas particuliers.

## Documenter avec des tests

On ajoutera à la documentation de la fonction une section **Exemples** qui reprendra une session d'appels de la fonction et de résultats sur un jeu de tests.

Par exemple :

def plus_grande_racine (a, b, c):
"""Renvoie la plus grande racine du polynôme de second degré ax^2 + bx + c

Paramètres :

- a : (int or float) coefficient a - associé à x^2

- b : (int or float) coefficient b - associé à x

- c : (int or float) coefficient c - constant

Valeur de retour :

- float (la plus grande racine du polynôme)
Contraintes :
- a doit être non nul
- (b**2 - 4*a*c) doit être positif ou nul
Exemples :
>>> plus_grande_racine(1, 1, -6)
2.0
>>> plus_grande_racine(5, 0, -5)
1.0
>>> plus_grande_racine(2, 2, 0)
0.0
"
"
pass

Notons que cette section Exemples de la documentation doit être écrite avant d'écrire la fonction. Il ne s'agit pas d'écrire la fonction, d'exécuter des appels à la fonction, et de recopier le résultat.

## Le module doctest

Le module `doctest` permet d'effectuer automatiquement les tests inclus dans la documentation `docstring`.

Pour lancer automatiquement les tests, il suffit d'utiliser la fonction `ad hoc` de ce module :

>>> import doctest
>>> doctest.testmod()

## Un premier test — échec

Avec la définition incomplète de notre fonction, nous obtenons bien entendu des erreurs :

>>> import doctest
>>> doctest.testmod()

**********************************************************************
File "__main__", line 13, in __main__.plus_grande_racine
Failed example:
    plus_grande_racine(1, 1, -6)
Expected:
    2.0
Got nothing
**********************************************************************
File "__main__", line 15, in __main__.plus_grande_racine
Failed example:
    plus_grande_racine(5, 0, -5)
Expected:
    1.0
Got nothing
**********************************************************************
File "__main__", line 17, in __main__.plus_grande_racine
Failed example:
    plus_grande_racine(2, 2, 0)
Expected:
    0.0
Got nothing
**********************************************************************
1 items had failures:
3 of 3 in __main__.plus_grande_racine
***Test Failed*** 3 failures.
TestResults(failed=3, attempted=3)

## Un second test — succès

En complétant notre fonction par un code « correct », nous obtenons :
>>> doctest.testmod()
TestResults(failed=0, attempted=3)

## Un test, en détail

Nous pouvons demander plus de détails à l'aide du paramètre **verbose** :

```
doctest.testmodعل (verbose=True)
Trying:
    plus_grande_racine(1, 1, -6)
Expecting:
    2.0
ok
Trying:
    plus_grande_racine(5, 0, -5)
Expecting:
    1.0
ok
Trying:
    plus_grande_racine(2, 2, 0)
Expecting:
    0.0
ok
1 items had no tests:
__main__
1 items passed all tests:
3 tests in __main__.plus_grande_racine
3 tests in 2 items.
3 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=3)
```

## Systématiser les tests

Mieux, il est possible de réaliser les tests à chaque fois que l'on exécute un fichier Python. Il suffit d'ajouter à la fin du fichier les 3 lignes

```
if __name__ == '__main__':
    import doctest
    doctest.testmodعل (verbose=True)
```

Tous les `doctest` des fonctions définies dans le fichier seront exécutés systématiquement lors de l'exécution du fichier.

## Memento

* la spécification des fonctions est complétée par ses contraintes d'utilisation

* la chaîne de documentation `dosctring` comporte une section **Contraintes**

* un jeu de tests est un ensemble de jeux de paramètres et résultats attendus de l'exécution d'une fonction

* la chaîne de documentation `dosctring` inclut un jeu de tests dans une section **Exemples**:

* le test unitaire est la vérification de la bonne exécution de ces tests

* le module `doctest` permet d'automatiser et systématiser l'exécution des tests unitaires
# Test de fonction avec L1test

## Coup d'œil

On découvre

* comment compléter la spécification et la documentation des fonctions par des exemples et tests

* comment automatiser les tests de nos fonctions

## Une fonction plus_grande_racine()

Nous désirons définir une fonction qui renvoie la plus grande racine d'un polynôme du second degré $ax^2 + bx + c$.

La fonction acceptera trois paramètres : les valeurs des coefficients $a, b$, et $c$.

Cette plus grande racine est évidemment égale à :

$$max(\frac{-b + \sqrt{\Delta}}{2a}, \frac{-b - \sqrt{\Delta}}{2a})$$

avec

$$\Delta = b^2 - 4ac$$

pourvu que $\Delta \ge 0$.

## Prototype et documentation

La première étape est de définir l'interface et la documentation de notre fonction `plus_grande_racine()`. On écrit par exemple :

```python
def plus_grande_racine (a, b, c):
    """Renvoie la plus grande racine du polynôme de second degré ax² + bx + c
```

Paramètres :

- a : (int or float) coefficient a - associé à $x^2$

- b : (int or float) coefficient b - associé à $x$

- c : (int or float) coefficient c - constant

Valeur de retour :

- float (la plus grande racine du polynôme)

"""

pass

L'exécution de l'instruction `pass` ne produit rien. Elle est utilisée pour remplacer du code qui n'est pas encore écrit car elle évite la génération d'une erreur lors de l'exécution d'un programme incomplet.

## Contraintes d'utilisation

L'utilisation de cette fonction `plus_grande_racine()` nécessite le respect de deux contraintes, appelées aussi conditions d'utilisation :
* $b^2 - 4ac \ge 0$

* $a \neq 0$

Il nous faut l'indiquer dans la documentation de la fonction. On écrira donc :

```python
def plus_grande_racine (a, b, c):
    """Renvoie la plus grande racine du polynôme de second degré ax² + bx + c
```

**Paramètres :**

- a : (int or float) coefficient a - associé à x²

- b : (int or float) coefficient b - associé à x

- c : (int or float) coefficient c - constant

**Valeur de retour :**

- float (la plus grande racine du polynôme)

**Contraintes :**

- a doit être non nul

- $(b**2 - 4*a*c)$ doit être positif ou nul

```python
"
```

pass

## Exemples d'utilisation

La documentation peut aussi être enrichie d'exemples d'utilisation de la fonction.

Si nous avions complètement défini notre fonction, nous pourrions l'utiliser dans l'interpréteur et obtenir les résultats suivants :

```
>>> plus_grande_racine(1, 1, -6)
```

2.0

```
>>> plus_grande_racine(4, -4, 1)
```

0.5

```
>>> plus_grande_racine(2, 2, 0)
```

0.0

Nous reconnaissions ici l' invite >>> de l'interpréteur Python telle qu'elle apparaît dans la console de Thonny.

Ces exemples associant appel de la fonction et résultat attendu sont précieux dans la documentation, car ils permettent à un utilisateur de la fonction d'éventuellement mieux comprendre la spécification de la fonction.

On va les écrire un peu différemment de telle sorte que le programmeur de la fonction puisse la **tester** automatiquement : c'est à dire s'assurer de manière automatique que, sur chacun de ces quelques exemples, le code de sa fonction produit bien le résultat attendu en réponse à l'évaluation de l'appel de la fonction.

L' invite de l'interpréteur Python sera remplacée par l' invite de la **bibliothèque de test L1test** utilisée dans l'UE, au choix :

* $$$$

* $py

* $PY

On pourra donc obtenir :

```python
def plus_grande_racine (a, b, c):
    """Renvoie la plus grande racine du polynôme de second degré ax² + bx + c
```

**Paramètres :**

- a : (int or float) coefficient a - associé à x²

- b : (int or float) coefficient b - associé à x

- c : (int or float) coefficient c - constant

**Valeur de retour :**

- float (la plus grande racine du polynôme)

**Contraintes :**

- a doit être non nul

- $(b**2 - 4*a*c)$ doit être positif ou nul

**Exemples :**

```
$$$ plus_grande_racine(1, 1, -6)
```
2.0
$$ plus_grande_racine(4, -4, 1)
0.5
$$ plus_grande_racine(2, 2, 0)
0.0
""
pass

## Tests unitaires, L1 test

Un `L1test`, ou simplement un test, est écrit dans la docstring de la fonction testée. Dans sa version la plus simple, il se compose de 2 lignes se suivant immédiatement :

```python
$$ <expression_à-évaluer>
<expression_résultatAttendu>
```

La documentation de la fonction `plus_grande_racine` contient donc 3 tests (on parle d'un jeu de tests). Pour le premier test, l'expression à évaluer est `plus_grande_racine(1, 1, -6)` et le résultat attendu est 2.0. Les valeurs des paramètres (ici 1, 1 et -6) sont appelées des données de test.

Éxécuter un test déclenche les évaluations suivantes :

* évaluation de l'expression qui suit l' invite : on obtient le résultat réel (pour l'exemple précédent 2.0) ;

* évaluation de l'expression sur la 2ème ligne : on obtient le résultat attendu (ici aussi 2.0) ;

* comparaison du résultat réel et du résultat attendu, ce qui produit le verdict du test.

Le mécanisme qui compare les valeurs réelle et attendue est appelé `oracle`<fnref n="1" />.

Il y a 3 verdicts possibles :

* le résultat réel et le résultat attendu sont les mêmes : c'est un succès, on dit que le test passe ;

* le résultat réel et le résultat attendu ne sont pas les mêmes : c'est un échec, on dit que le test échoue (failure en anglais) ;

* une erreur se produit, qui empêche le calcul du résultat réel et/ou attendu : c'est une erreur.

## Conventions d'affichage de L1test

Dans la fenêtre L1test de Thonny, les conventions sont les suivantes :

* succès : la couleur associée est le `vert` et le message indique que le test est OK ;

* échec/failure : la couleur associée est le `rouge` et le message indique `test failed` ;

* erreur : la couleur associée est aussi le `rouge` et le message indique `test raised exception`, la levée d'exception étant le mécanisme par lequel Python gère les erreurs.

## Exemples

### Avec un code réduit à pass

Si on reprend la fonction `plus_grande_racine` avec son corps réduit à l'instruction `pass`, le code ne contient aucune instruction `return`. Dans ce cas, Python renvoie par convention la valeur spéciale `None`. Pour les 3 tests le résultat réel est donc `None` et les 3 tests échouent avec un message du type :

```
Test failed for: plus_grande_racine(1, 1, 6)
Expected: 2.0, Got: None
```

ce qu'on peut traduire par "la valeur attendue est 2.0 alors que la valeur réelle est None".

### Avec un code correct

On suppose maintenant qu'on a écrit un corps de fonction correct pour `plus_grande_racine`<fnref n="2" />.

Supposons que la docstring contienne le test suivant :

```python
$$ plus_grande_racine(1, 1, -6)
2.0
```

<footnote n="1">¹On verra plus tard comment se fait exactement cette comparaison en Python.</footnote>

<footnote n="2">²Un code est correct s'il est conforme à sa spécification, l'énoncé de l'exercice par exemple.</footnote>
Oups ! Il manque un caractère $ dans l' invite... ces 2 lignes sont donc considérées comme du texte libre dans
une docstring et pas comme un L1test, qui n'est donc pas exécuté.

Supposons maintenant que la docstring contienne le test suivant :

```python
$$$ plus_grande_racine(1, 1, -6)
20
```

Cette fois il manque le . dans la valeur attendue, le test échoue avec un message en rouge du type :

```
Test failed for: plus_grande_racine(1, 1, 6)
Expected: 20, Got: 2.0
```

Si par inadvertance nous écrivons un test *qui n'a pas de sens*, en utilisant une valeur de test qui viole les conditions d'utilisation de la fonction<fnref n="3" /> avec la valeur 0 pour a :

```python
$$$ plus_grande_racine(0, 1, -6)
2.0
```

La division par 0 produira une erreur et le test est en erreur avec un message en rouge du type :

```
Test raises exception for: plus_grande_racine(0, 1, -6)
Zero division error: float division by 0
```

Les 3 tests précédemment écrits dans la docstring initiale de `plus_grande_racine` passent, avec un message en vert du type :

```
Test OK for: plus_grande_racine(1, 1, -6)
```

### Avec un code incorrect

Écrivons maintenant un code faux, par exemple en prévoyant comme valeur de racine $\frac{b-\sqrt{\Delta}}{2a}$ au lieu de $\frac{-b-\sqrt{\Delta}}{2a}$.
Parmi les 3 tests initialement prévus, seul le premier test échouera, avec un message en rouge du type :

```
Test failed for: plus_grande_racine(1, 1, 6)
Expected: 2.0, Got: -2.0
```

On note au passage que les 2 autres tests ne détectent pas l'erreur dans le code.

## Questions fréquentes

### Pourquoi écrire les tests avant le code ?

* pour vérifier qu'on a bien compris le travail que doit effectuer la fonction à coder, d'après sa spécification ;

* parce que si on écrit les tests après avoir écrit le code, on risque de choisir des valeurs attendues en regardant le code, et non la spécification de la fonction. On risque donc d'écrire des tests faux qui valident un code faux ! (et ça n'arrive pas qu'aux débutants !) ;

* les méthodes actuelles de développement logiciel sont des méthodes "test first".

### Si un test échoue, c'est que le code de la fonction est faux ?

Soit le code de la fonction est faux, soit le test est mal écrit.

### Si tous les tests passent, le code est correct ?

**Malheureusement non.** Exécuter des tests peut mettre en évidence une erreur dans le code d'une fonction, mais ne peut pas prouver que le code est correct. En effet on peut très rarement tester toutes les valeurs que peuvent prendre les paramètres d'une fonction. Mais des tests au vert permettent d'avoir une plus grande confiance dans la correction du code, surtout si les jeux de tests sont bien choisis.

### Comment bien choisir ses jeux de tests ?

C'est un art qui s'acquiert avec l'expérience.

On choisira classiquement des **valeurs représentatives** des différents comportements. Dans le calcul de la plus grande racine on a choisi d'abord un test impliquant un discriminant strictement positif, puis un test impliquant

<footnote n="3">³Une fonction doit par convention être appelée avec des valeurs des paramètres qui satisfont ses conditions d'utilisation.</footnote>
un discriminant nul. Le dernier test semble un peu redondant avec le premier, même s'il montre que la valeur 0 est acceptable pour c. Par contre aucun test n'implique de coefficient flottant, ce qui est dommage.

On choisira aussi des valeurs dites “**aux limites**”, c’est à dire qui illustrent les bornes du domaine de la fonction. Si l’énoncé stipule qu’un paramètre *x* doit être positif, on ne sait pas trop si la valeur 0 est acceptable. Si elle l’est, on choisira cette valeur comme donnée de test.

## C'est vraiment utile de tester ?

C'est la méthode de validation de programme la plus utilisée, y compris dans le monde professionnel. Pour vous, c'est une excellente manière de savoir "si vous avez bon" sur ordinateur, l'autre manière étant la relecture de code.

## Peut-on toujours tester une fonction ?

Avec L1test, non. On verra par exemple dans le chapitre suivant comment tester une fonction qui a un comportement aléatoire. D'autres bibliothèques de test plus avancées existent, enseignées en Licence et Master mention Informatique.

## Et si les calculs sont approchés ?

Dans le jeu de tests de `plus_grande_racine` on aurait pu utiliser des coefficient à valeurs réelles, par exemple `plus_grande_racine(1.5, 1, -5.2)`. Dans ce cas ce n'est pas facile de prévoir le résultat de la fonction de tête, mais on peut dérouler les calculs du discriminant et des racines dans l'interpréteur Python. On s'aperçoit alors que le résultat est `1.558168127881481`, ce qui n'est pas très illustratif. Comme d'une manière générale les calculs sur flottants sont approchés, il est problématique de prévoir un résultat exact. On peut alors utiliser un arrondi dans les tests impliquant un résultat de type flottant. Par exemple :

```python
$$$$ round(plus_grande_racine(1.5, 1, -5.2), 2)
1.56
```

## Memento

* la spécification des fonctions est complétée par ses contraintes d'utilisation

* la chaîne de documentation `dosctring` comporte une section `Contraintes`

* un jeu de tests est un ensemble de tests incluant appel de fonction sur des données de test et résultat attendu de l'exécution de l'appel

* la chaîne de documentation `dosctring` inclut un jeu de tests dans une section `Exemples`

* selon le résultat de son exécution, un test peut être un succès, un échec ou être en erreur

* des tests qui passent augmentent la confiance qu'on peut avoir dans son code, mais il peut y subsister des erreurs non détectées

* l'outil L1test permet d'automatiser et systématiser l'exécution des tests unitaires
# Exercices niveau débutant

## Utilisation et importation de fonction

### Devinez le résultat

21. Dans un premier temps, anticipez la valeur prise par les variables à l'issue de l'exécution de chacune des instructions données.

Puis, vérifierez si la valeur que vous avez proposée est exacte en

* copiant ces lignes de code dans un programme que vous exécuterez à l'aide de Thonny,

* consultant les valeurs des variables définies dans le shell de Thonny ou via le panneau des variables.

```python
res1 = len('Quelques mots')
res2 = len("Autre exemple !")
res3 = len(21)
res4 = len('21')
res5 = str(21.1)
res6 = len(res5)
res7 = max(12,8, 12.3, 4)
res8 = max(12, 8, 12.3, 4)
res9 = max(len("compare"), len(" compare"), len(" compare"))
resA = min(len("9"), len("1.1"), len("10"), len("-3"))
resB = max(len("9"), len("len('1.1')"), len("11"))
```

### Découvrez une fonction

22. À l'aide de la fonction `help()`, consultez la documentation de la fonction `log()` du module `math`.

Puis, calculez les valeurs des expressions $log_{10} 100$ et $ln 2$.

Vous devez obtenir, respectivement 2 et environ 0,693.

### Calculez avec des fonctions mathématiques

#### Cercle

23. Écrivez des expressions qui permettent de calculer les valeurs suivantes pour un rayon $R = 3$ cm :

* circonférence d'un cercle : $2\pi R$
* aire d'un disque : $\pi R^2$

Vous devez obtenir environ 18,849 cm pour la circonférence et 28,274 cm² pour l'aire.

24. Faites de même pour un rayon de 25 m.

Vous devez obtenir environ 157,079 m pour la circonférence et 1963,495 m² pour l'aire.

#### Trigonométrie

25. Sachant que $cos 180° = cos \pi = -1$, testez si les fonctions trigonométriques du module `math` prennent en paramètre des angles en degrés ou en radians.

26. Soit un triangle rectangle illustré ci-dessous, l'égalité suivante s'applique : $cos(\alpha) = \frac{AC}{AB}$
<figure><img src="image_4.png" /></figure>

Écrivez une suite d'instructions pour calculer la valeur de AC, sachant que l'angle $\alpha$ mesure 30°, soit $\pi/6$ radians, et que AB mesure 3 cm.

Vous devez obtenir environ 2,598 cm pour la valeur de AC.

27. Faites de même pour un triangle rectangle où $\alpha = 45° = \pi/4$ et $AB = 11$ cm.

Vous devez obtenir environ 7,778 cm pour la valeur de AC.

### Utilisez l'aléatoire

28. Générez un nombre entier aléatoire compris entre 1 et 6.

Exécutez plusieurs fois la ou les instructions pour observer les valeurs obtenues lors d'exécutions successeuses.

29. Écrivez des instructions afin de choisir au hasard un caractère parmi les suivants : « + - / * = ». De même, exécutez plusieurs fois ces instructions pour observer les différentes valeurs obtenues.

## Définition et test de fonction

### Documentez une fonction

30. Copiez-collez le code complet - y compris la documentation - de la fonction `celsius2fahrenheit()` vue dans le cours *Définition de fonctions*.

31. Affichez la documentation de cette fonction dans le shell.

32. Sachant que les températures suivantes 30°C, 0°C et -5°C, correspondent, respectivement, à 86°F, 32°F et 23°F, ajoutez trois exemples à la documentation de cette fonction.

33. Ajoutez le code nécessaire pour systématiser les tests unitaires pour cette fonction.
# Exercices niveau intermédiaire

## Définition et test de fonction

Pour chaque fonction demandée, commencez par écrire la documentation complète, y compris les tests unitaires que vous utiliserez avec le module `doctest`. N'oubliez pas de préciser les contraintes d'utilisation, écrivez `Contrainte : aucune` si vous pensez qu'il n'y en a pas.

## Des instructions aux fonctions

Vous allez reprendre les instructions codées dans les exercices précédents de niveau débutant, et les transformer en fonctions.

Vous utiliserez les valeurs données en exercice pour construire vos tests.

*Remarque : certains tests unitaires sont fastidieux, voire impossibles pour l'instant, à mettre en place. Lesquels et pourquoi ? Nous verrons dans les prochaines séances comment faire autrement.*

34. Écrivez une fonction `cercle_circonference()` qui prend en paramètre la longueur d'un rayon et renvoie la circonférence d'un cercle ayant ce rayon.

35. Écrivez une fonction `cercle_aire()` qui prend en paramètre la longueur d'un rayon et renvoie l'aire d'un cercle ayant ce rayon.

36. Écrivez une fonction `coteadjacent()` qui prend en paramètre un angle $\alpha$ et la longueur de l'hypoténuse et qui renvoie la longueur du côté adjacent à l'angle $\alpha$.

Vous pouvez maintenant calculer, par exemple, l'aire d'un cercle avec n'importe quelle valeur de rayon en une seule instruction, alors que vous deviez en écrire plusieurs sans passer par une fonction.

37. Écrivez une fonction `de_6()` qui simule le lancé d'un dé, c'est-à-dire qui ne prend pas de paramètre et qui renvoie un nombre aléatoire compris entre 1 et 6 inclus.

Appelez plusieurs fois cette fonction `de_6()` pour simuler plusieurs lancés de dés.

Écrivez une fonction `de()` généralisant la précédente et qui simule le lancé d'un dé dont le nombre de faces est donné en paramètre, et renvoie un nombre aléatoire compris entre 1 et ce nombre de faces inclus.

38. Écrivez une fonction `choisi_caractere()` qui prend en paramètre une chaîne de caractères et qui renvoie un des caractères de cette chaîne choisi aléatoirement.

Écrivez également plusieurs appels à la fonction `choisi_caractere()` pour observer les valeurs renvoyées.

39. Écrivez une fonction `calcule_interets()` qui reprend la formule de l'exercice de calcul d'intérêts donné dans la feuille d'exercices sur les variables, de niveau intermédiaire. Définissez vous-même quels doivent être les paramètres de cette fonction.

## Codez des fonctions simples

40. Écrivez la fonction `addition()` qui prend deux nombres en paramètres et renvoie leur somme.

41. Écrivez la fonction `carre()` qui renvoie le carré d'un nombre passée en paramètre.

42. Écrivez la fonction `cube()` qui renvoie le cube d'une valeur passée en paramètre.
## Enluminures

43. Écrivez une série de fonctions `enluminure1()` ... `enluminure5()` qui prennent en paramètre une chaîne de caractères quelconque et qui renvoient une autre chaîne (décorative) qui a la forme ci-dessous et ayant un nombre de caractères aussi proche que possible de la longueur de la chaîne passée en paramètre.

1. de la forme « ---------- »

2. de la forme « ----0---- »

3. de la forme « -o-o-o-0-o-o-o- »

4. de la forme « >>-o-o-o-0-o-o-o-<< »

5. de la forme « <->-<->-<->-0-<->-<->-<-> »

Par exemple, `enluminure2('Python')` produira la chaîne « ---0--- ».

## Utilisez et combinez des fonctions

Dans la console Python, utilisez uniquement les fonctions définies précédemment afin de répondre aux questions suivantes (même les opérateurs arithmétiques sont interdits). Chaque calcul devra être codé en une seule expression.

44. Calculez le carré de 0,01. Quelle valeur obtenez-vous ? D'après-vous, pourquoi ?

45. Calculez l'aire (ou surface) d'un carré de 4 cm de côté.

Vous devez obtenir 16 cm².

46. Calculez l'aire d'un rectangle de dimensions (en cm) : 4 × 8. Vous noterez que ce rectangle équivaut à deux carrés de 4 cm de côté, placés côte à côte.

Vous devez obtenir 32 cm².

47. Calculez le volume d'un cube de 5,5 cm de côté.

Vous devez obtenir 166,375 cm³.

48. Calculez le volume d'un pavé de dimensions (en cm) : 4 × 4 × 8. Vous noterez que ce pavé équivaut à deux cubes de 4 cm de côté, placés côte à côte.

Vous devez obtenir 128 cm³.

49. Réalisez deux lancers de dé et additionnez leurs valeurs.

Il n'est pas possible de savoir quelle valeur sera obtenue, la seule certitude est qu'elle sera comprise entre 2 et 12.

50. Écrivez une instruction qui permet de générer aléatoirement une syllabe composée d'une consonne suivi d'une voyelle.

Ici aussi, il n'est pas possible d'anticiper la syllabe générée, la seule certitude est que la première lettre sera présente dans la chaîne de caractères "zrtpqsdfghjklmwxcvbn" et la deuxième dans la chaîne "aeiouy"

## En secondes !

Un jour est composé de 24 heures, une heure de 60 minutes, et une minute de 60 secondes.

Sur le modèle suivant

```python
def minute2sec(m):
    """Renvoie le nombre de secondes correspondant à un nombre de
    minutes donné.
    Paramètre :
        m (int) : nombre de minutes à convertir
    Valeur de retour :
        int : nombre de secondes
    Contraintes : m positif ou nul
    Exemples :
        >>> minute2sec(0)
        0
        >>> minute2sec(42)
```
51. Proposez trois fonctions

1. `minute2sec()` de conversion de minutes en secondes

2. `heure2min()` de conversion d'heures en minutes

3. `jour2heure()` de conversion de jours en heures

52. Proposez ensuite une fonction de conversion en seconde d'une durée exprimées en jour, heures, minutes secondes.

Cette fonction `en_seconds()` acceptera donc quatre paramètres : un nombre de jours, un nombre d'heures, un nombre de minutes, et un nombre de secondes.

On utilisera les fonctions précédemment définies.
# Exercices niveau confirmé

## Calcul en chaîne

Soit le programme suivant, `calc0.py` :
(pour les besoins de l'exercices, les fonctions ne comportent pas de docstring)

a = 5

b = 1

def calc0(b, a):
    return (b-a)//2

On charge le fichier et on exécute ce programme Python dans l'environnement Thonny.

53. Quelles sont les variables et fonctions définies suite à cette exécution ?

Vérifiez votre réponse en consultant le volet *Variables* de Thonny.

Suite à cette exécution, on saisit le code suivant dans la console Python (*Shell*).

a = calc0(a, b)

54. Quelle est alors la valeur associée à la variable a ?

On considère maintenant le programme suivant, `calc1.py` :

a = 5

b = 1

c = 2

def calc1(a, b):
    return (a+b)//2

def calc2(a, b):
    return (a-b)//2

def calc3(x, y):
    return calc1(x, y) * calc2(y, x)

On charge le fichier et on exécute ce programme Python dans l'environnement Thonny.

55. Quelles sont les variables et fonctions définies suite à cette exécution ?

Vérifiez votre réponse en consultant le volet *Variables* de Thonny.

Suite à cette exécution, on saisit le code suivant dans la console Python (*Shell*).

b = calc3(c, a+b)

56. Quelle est maintenant la valeur associée à la variable b ?

On complète le programme `calc1.py` avec le code suivant :

def calc4(b, c):
    x = 1
    y = c-b
    z = calc3(y, 1)
    return calc1
c = calc4(b//2, calc1(a,c))

57. Quelle est la valeur associée à chacune des variables a, b, et c suite à une nouvelle exécution du programme ?

## Babbage

En 1822, Charles Babbage imagine les plans d'une machine à calculer les valeurs successives d'un polynôme par la méthode des différences, remplaçant ainsi des multiplications et des élévations à une puissance entière par des suites d'additions.

### Calcul d'un polynôme de degré 1

Soit à calculer le polynôme $f(x) = 3x + 5$ pour $x$ prenant successivement les valeurs 0, 1, 2, ...

58. Définissez une fonction f() à un paramètre x qui renvoie la valeur de ce polynôme en x.
Cette fonction pourra servir à vérifier les calculs menés dans la suite.

Le tableau suivant illustre un calcul possible pour obtenir les valeurs successives de $f(x)$ :

* la première colonne contient les valeurs de $x$,

* la colonne suivante la valeur du polynôme pour $x$,

* et la troisième colonne les différences entre deux valeurs successives du polynôme.

<table><thead><tr><th>x</th><th>f(x)</th><th>g(x) = f(x + 1) - f(x)</th></tr></thead><tbody><tr><td>0</td><td>5</td><td>3</td></tr><tr><td>1</td><td>8</td><td>3</td></tr><tr><td>2</td><td>11</td><td>3</td></tr><tr><td>3</td><td>14</td><td>...</td></tr></tbody></table>

On définit deux variables et une constante que l'on initialise avec les valeurs de la première ligne de ce tableau :

x = 0
fx = 5

GX = 3

59. Écrivez une suite d'instructions qui calcule dans x et fx les valeurs de la ligne suivante.

60. Exécutez ces instructions autant de fois que nécessaire pour compléter le tableau avec les valeurs de $f(4)$ et $f(5)$.

### Avec un polynôme de degré 5

Considérons maintenant le polynôme $f(x) = 5x^5 + 2x^4 + 3x^2 + 7x + 1$.

Complétez un peu le tableau suivant :

<table><thead><tr><th>x</th><th>f(x)</th><th>g(x) = f(x + 1) - f(x)</th><th>h(x) = g(x + 1) - g(x)</th><th>j(x)</th><th>k(x)</th><th>l(x) = k(x + 1) - k(x)</th><th>...</th></tr></thead><tbody><tr><td>0</td><td>1</td><td>17</td><td>184</td><td>822</td><td>1248</td><td>600</td><td>...</td></tr><tr><td>1</td><td>18</td><td>201</td><td>1006</td><td>2070</td><td>1848</td><td>600</td><td>...</td></tr><tr><td>2</td><td>219</td><td>1207</td><td>3076</td><td>3918</td><td>2448</td><td>...</td><td>...</td></tr><tr><td>3</td><td>1426</td><td>4283</td><td>6994</td><td>6366</td><td>3048</td><td>...</td><td>...</td></tr><tr><td>4</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table>

61. Combien de colonnes utiles contient ce tableau en fonction du degré du polynôme ?

62. Définissez six variables et une constante initialisées avec les valeurs de la première ligne.

63. Donnez la suite d'instructions qui, chaque fois qu'elle sera exécutée, calculera la valeur du polynôme pour la valeur suivante de $x$.
Affectation multiple

Python autorise des affectations multiples.

64. Exécutez les instructions suivantes et observez le résultat à chaque étape :

a, b, c = 12, 1, 7

a, b, c = a+b, b+c, c+a

Chacune des expressions de la partie droite de l'affectation est évaluée.

Puis, les variables de la partie gauche de l'affectation sont associées aux valeurs produites par ces évaluations.

65. Utilisez cette affectation multiple pour réduire la suite d'instructions de calcul du polynôme précédent à
une seule instruction.
3. Type booléen - Expressions
booléennes - Prédicats
# Expressions booléennes

## Coup d'œil

On découvre

* comment manipuler des booléens, les valeurs de vérité `vrai` et `faux`

* comment comparer des nombres ou des chaînes de caractères

* comment construire des expressions booléennes

* comment définir des prédicats, fonctions renvoyant `vrai` ou `faux`

## Les booléens

Le type booléen a deux valeurs - `vrai` et `faux` -.

Python définit deux littéraux `True` et `False` pour noter ces valeurs :

```
>>> True
True
>>> type(True)
<class 'bool'>
>>> False
False
```

La casse est signifiante : `True` et `False` s'écrivent avec une majuscule en début de mot.

Une expression dont la valeur ne peut être que `vrai` ou `faux` est appelée **expression booléenne**.

Une fonction à valeur booléenne est appelée **prédicat**.

## Opérateurs de comparaison

Les opérateurs de comparaison, comme leur nom l'indique, permettent de comparer deux valeurs et de produire une valeur booléenne.

La valeur `12` est inférieure à la valeur `42`, `vrai` ou `faux` ?

```
>>> 12 < 42
True
```

Python définit les opérateurs suivants :

<table><thead><tr><td>comparaison</td><td>opérateur Python</td><td>commentaire</td></tr></thead><tbody><tr><td>égalité</td><td>==</td><td>à ne pas confondre avec l'affectation =</td></tr><tr><td>différence</td><td>!=</td><td>l'ordre des caractères est important</td></tr><tr><td>inférieur</td><td>&lt;</td><td></td></tr><tr><td>inférieur ou égal</td><td>&lt;=</td><td>l'ordre des caractères est important</td></tr><tr><td>supérieur</td><td>&gt;</td><td></td></tr><tr><td>supérieur ou égal</td><td>&gt;=</td><td>l'ordre des caractères est important</td></tr></tbody></table>
## Comparer des nombres

Nous pouvons comparer des nombres :

>>> a = 6

>>> b = 4

>>> c = 2

>>> a < b

False

>>> a < b+c

False

>>> a == b+c

True

>>> c != a-b

False

Nous pouvons comparer des nombres de différents types :

>>> 1 + 1 == 2.0

True

## Comparer des flottants

Les flottants sont des approximations de valeurs entières. La comparaison de flottants nécessite de prendre des précautions.

>>> z = 3.0

>>> 3.0 == z

True

>>> 3 * 0.1 == 0.3

False

>>> 3 * 0.1

0.30000000000000004

Nous verrons qu'il est préférable de procéder en vérifiant si l'écart entre deux valeurs est inférieur à un seuil donné à définir, par exemple 0.00000001.

## Égalité de chaînes de caractères

Deux chaînes de caractères sont égales si et seulement si :

* elles ont le même nombre de caractères

* le premier caractère de chacune des deux chaînes sont identiques

* le deuxième caractère de chacune des deux chaînes sont identiques

* etc.

On utilise les opérateurs `==` et `!=` sur les chaînes :

>>> 'oui' == 'o' + 'u' + 'i'

True

>>> 'oui' == 'oui '

False

>>> 'oui' != 'Oui '

True

>>> "C cool !" == 'C cool !'

True

>>> "C'est cool !" == 'C\'est cool !'

True

Notons que la comparaison de chaînes de caractères est sensible à la casse : les majuscules et minuscules sont des caractères différents.

De plus, l'espace est aussi un caractère.

Par ailleurs, des valeurs littérales différentes permettent d'écrire une même chaîne de caractères (par exemple en utilisant les apostrophes ' ou les guillemets "). Il s'agit cependant bien de la même valeur, la comparaison par `==` donne vrai.
# Prédicats

Un prédicat est donc une fonction qui renvoie une valeur booléenne.

## Premier exemple — parité

Définissons un prédicat `est_pair()` pour vérifier la parité d'un entier donné. Voici sa spécification :

```python
def est_pair(n):
    """
    Renvoie True si et seulement si n est pair, False sinon.

    Paramètres :
        n : entier dont on teste la parité
    Valeur de retour :
        bool
    Contrainte : aucune
    Exemples :
        >>> est_pair(42)
        True
        >>> est_pair(13)
        False
        >>> est_pair(0)
        True
        """
```

Un entier est pair si le reste de sa division entière par 2 est nul.

On pourra donc compléter notre fonction avec le code suivant :

```python
def est_pair(n):
    """
    [...]
    """
    return n%2 == 0
```

Vérifions que ce code valide bien les tests :

```python
>>> import doctest
>>> doctest.testmod(verbose=True)
Trying:
    est_pair(42)
Expecting:
    True
ok
Trying:
    est_pair(13)
Expecting:
    False
ok
Trying:
    est_pair(0)
Expecting:
    True
ok
1 items had no tests:
    __main__
1 items passed all tests:
    3 tests in __main__.est_pair
    3 tests in 2 items.
    3 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=3)
```
## Deuxième exemple — somme paire

Définissons un prédicat qui vérifie si la somme de deux nombres est paire.

Voici une spécification :

def somme_est_paire(a, b):

###

Renvoie True si et seulement si la somme de deux nombres est paire.

Paramètres:

- a : entier

- b : entier

Valeur de retour: booléen

Contrainte : aucune

Exemples:

>>> somme_est_paire(21, 42)

False

>>> somme_est_paire(42, -2*21)

True

>>> somme_est_paire(1, 3)

True

###

Nous pouvons compléter le code de cette fonction par :

somme = a+b

return somme%2 == 0

ou faire appel à notre fonction est_pair() :

somme = a+b

return est_pair(somme)

La deuxième solution est à privilégier car elle évite la redondance de code.

## Troisième exemple — chaîne non vide

Définissons maintenant un prédicat est_non_vide() qui correspond à la spécification suivante :

def est_non_vide(str):

###

Renvoie True si et seulement si une chaîne de caractères n'est pas vide.

Paramètre:

- str : chaîne de caractères

Valeur de retour: booléen

Contrainte : aucune

Exemples:

>>> est_non_vide('Timoléon')

True

>>> est_non_vide('')

False

>>> est_non_vide(' ')

True

###

Le code de cette fonction est par exemple :

return str != ''
Memento

* le type *booléen* *bool* permet de représenter les valeurs de vérité *vrai* et *faux*

* en Python, on utilise les mots-clés *True* et *False*

* les opérateurs de comparaison testent l'égalité, la différence, l'inférieur, le supérieur de deux valeurs

* un opérateur de comparaison produit une valeur booléenne

* en Python, l'opérateur d'égalité est noté `==`, l'opérateur de non égalité est noté `!=`

* un prédicat est une fonction qui renvoie une valeur booléenne
# Opérateurs logiques

## Coup d'œil

On découvre

* comment combiner des valeurs booléennes pour en produire d'autres

* les opérateurs logiques et, ou, non

* comment une table de vérité définit un opérateur logique

## Opérateurs booléens

Les opérateurs booléens, aussi appelés opérateurs logiques, combinent des valeurs booléennes pour produire une nouvelle valeur booléenne.

Il existe principalement trois opérateurs logiques :

* la négation - le non -,

* la conjonction - le et logique -,

* la disjonction - le ou logique -

### La négation – not

L'opérateur de négation permet d'obtenir la valeur inverse d'une valeur booléenne.

En Python, l'opérateur de négation est noté not . On écrit par exemple

>>> pair = True
>>> not pair
False

ou

>>> not 3 < 42
False
>>> not 'Zim' == 'Zac'
True

On décrit couramment les opérateurs booléens par leur table de vérité. Pour l'opérateur de négation :

<table><thead><tr><th>x</th><th>not x</th></tr></thead><tbody><tr><td>False</td><td>True</td></tr><tr><td>True</td><td>False</td></tr></tbody></table>

### Le et logique — and

Le et logique permet d'exprimer le fait que deux expressions sont vraies simultanément : x and y est vrai si et seulement si x est vrai, et y est vrai aussi.

En Python, le et logique est noté and . On écrit par exemple
>>> pair = True
>>> positif = False
>>> pair and positif
False

ou

>>> a = 12
>>> a < 42 and a > 0
True
>>> 'Zim' != 'Zac' and 'Zim' != 'Zou'
True

La table de vérité de l'opérateur *et logique* est la suivante

<table><thead><tr><th>x</th><th>y</th><th>x and y</th></tr></thead><tbody><tr><td>False</td><td>False</td><td>False</td></tr><tr><td>False</td><td>True</td><td>False</td></tr><tr><td>True</td><td>False</td><td>False</td></tr><tr><td>True</td><td>True</td><td>True</td></tr></tbody></table>

## Le ou logique — or

Le *ou logique* permet d'exprimer le fait qu'une parmi deux expressions est vraie : *x* or *y* est vrai si soit *x* est vrai, soit *y* est vrai, soit les deux sont vrais.

Remarquons que *x* or *y* est aussi vrai si *x* est vrai et *y* est vrai.

En Python, le *ou logique* est noté **or**. On écrit par exemple

>>> pair = True
>>> positif = False
>>> pair or positif
True

ou

>>> a = 12
>>> a < 0 or a > 42
False
>>> 'Zim' == 'Zac' or 'Zim' == 'Zou'
False

La table de vérité de l'opérateur *ou logique* est la suivante

<table><thead><tr><th>x</th><th>y</th><th>x or y</th></tr></thead><tbody><tr><td>False</td><td>False</td><td>False</td></tr><tr><td>False</td><td>True</td><td>True</td></tr><tr><td>True</td><td>False</td><td>True</td></tr><tr><td>True</td><td>True</td><td>True</td></tr></tbody></table>

## Expressions booléennes

Une expression booléenne est une expression à valeur booléenne.

Définissons quelques prédicats qui utilisent des expressions booléennes ou des opérateurs booléens.

### Premier exemple – pair positif

Nous avions défini un prédicat `est_pair()` qui vérifie la parité d'un entier donné en paramètre.

Définissons un prédicat `est_pair_positif()` dont voici une spécification :
```python
def est_pair_positif(n):
    """
    Renvoie True si n est pair positif, False sinon.
    Paramètre :
        - n : entier dont on teste la parité et le signe
    Valeur de retour :
        - booléen
    Contraintes : aucune
    Exemples:
        >>> est_pair_positif(42)
        True
        >>> est_pair_positif(-12)
        False
        >>> est_pair_positif(11)
        False
        >>> est_pair_positif(0)
        True
        """
```

Nous pouvons nous baser sur un appel à notre prédicat `est_pair()` et vérifier que le paramètre `n` est positif :

```python
pair = est_pair(n)
positif = n >= 0
return pair and positif
```

ou écrire directement

```python
return est_pair(n) and n >= 0
```

ou encore

```python
return n%2 == 0 and n >= 0
```

## Second exemple – Zim Zac Zou

Définissons un prédicat qui vérifie qu'une chaîne de caractères est une des deux chaînes « Zim », « Zac » :

```python
def est_zim_zac(str):
    """
    Renvoie True si str est une des chaînes 'Zim', 'Zac'
    Paramètre :
        - str : chaîne de caractères
    Valeur de retour :
        - booléen
    Contraintes : aucune
    Exemples:
        >>> est_zim_zac('Zac')
        True
        >>> est_zim_zac('Zim Zac')
        False
        >>> est_zim_zac('')
        False
        """
```

Il s'agit donc de vérifier si la chaîne est égale à « Zim » : `str == 'Zim'`, ou si la chaîne est égale à « Zac » : `str == 'Zac'`.

On peut donc écrire

```python
return str == 'Zim' or str == 'Zac'
```

Complétons notre prédicat pour vérifier qu'une chaîne de caractères est une des trois chaînes « Zim », « Zac », « Zou ».

Il s'agit de vérifier si la chaîne est égale à une des deux chaînes « Zim », « Zac » (ce que nous venons de faire), ou si la chaîne est égale à « Zou ».
On écrira :

def est_zim_zac_zou(str):

"
""
Renvoie True si str est une des chaînes 'Zim', 'Zac', 'Zou'
Paramètre:
- str : chaîne de caractères
Valeur de retour :
- booléen
Contraintes : aucune
Exemples:
>>> est_zim_zac_zou('Zac')
True
>>> est_zim_zac_zou('Zim Zac')
False
>>> est_zim_zac_zou('')
False
"""
return str == 'Zim' or str == 'Zac' or str == 'Zou'

Opérateurs booléens séquentiels

Observons la table de vérité de l'opérateur and :

<table>
   <thead>
    <tr>
     <td>
      x
     </td>
     <td>
      y
     </td>
     <td>
      x and y
     </td>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>
      False
     </td>
     <td>
      False
     </td>
     <td>
      False
     </td>
    </tr>
    <tr>
     <td>
      False
     </td>
     <td>
      True
     </td>
     <td>
      False
     </td>
    </tr>
    <tr>
     <td>
      True
     </td>
     <td>
      False
     </td>
     <td>
      False
     </td>
    </tr>
    <tr>
     <td>
      True
     </td>
     <td>
      True
     </td>
     <td>
      True
     </td>
    </tr>
   </tbody>
  </table>

Pour évaluer x and y :

• si x est évalué à False

• quelque soit la valeur de y, x and y vaudra False

Python va utiliser cette remarque pour définir l'opérateur and . Ainsi, pour évaluer l'expression x and y :

• Python évalue la partie gauche, le x

• si elle vaut False , on a terminé

• sinon, Python évalue la partie droite, le y

On parle d'opérateur séquentiel : la partie gauche est évaluée. Puis, si nécessaire, la partie droite est évaluée.

Il en est de même pour l'opérateur or .

Memento

• un opérateur logique combine des valeurs booléennes et produit une valeur booléenne

• l'opérateur logique de négation est not en Python

• l'opérateur et logique est noté and en Python

• l'opérateur ou logique est noté or en Python

• on combine ces opérateurs logiques pour former des expressions booléennes

• les opérateurs logiques de Python sont séquentiels
# Expressions booléennes et doctests

## Coup d'œil

On découvre :

* l'utilité des expressions booléennes pour écrire certains doctests

* un moyen de tester l'égalité de valeurs flottantes, en particulier dans les doctests

* un moyen de tester l'égalité de chaînes de caractères, en particulier dans les doctests

## Expression booléenne

Une expression booléenne est une expression dont la valeur est un booléen.
En Python, ce sera *True* ou *False*.

Vérifier qu'une expression quelconque est égale à un résultat donné peut s'écrire à l'aide d'une expression booléenne.

Par exemple vérifier qu'un appel de la fonction `plus_grande_racine(1, 1, -6)` produit bien la valeur 2 peut s'écrire :

`plus_grande_racine(1, 1, -6) == 2.0`

On peut aussi désigner la valeur 2 par l'entier Python 2, et écrire :

`plus_grande_racine(1, 1, -6) == 2`

## Tests de fonctions renvoyant des nombres

Dans la spécification de notre fonction `plus_grande_racine()`, nous écrivions :

```python
def plus_grande_racine(a, b, c):
```

```
"""
```

Renvoie la plus grande racine du polynôme de second degré $ax^2 + bx + c$

Paramètres :

- a : (int or float) coefficient a - associé à $x^2$

- b : (int or float) coefficient b - associé à x

- c : (int or float) coefficient c - constant

Valeur de retour :

- float (la plus grande racine du polynôme)

Contraintes :

- a doit être non nul

- (b**2 - 4*a*c) doit être positif ou nul

Exemples :

`>>> plus_grande_racine(1, 1, -6)`

2.0

```
"""
```

Mais il nous importe peu que la fonction renvoie `2.0` ou `2`.

On écrira donc notre doctest sous la forme d'une expression booléenne. Par exemple :
```python
>>> plus_grande_racine(1, 1, -6) == 2
True
```

## Tests de fonctions renvoyant des réels

Il est impossible de prévoir la valeur exacte d'un réel renvoyé par une fonction à cause des approximations et arrondis.

Il est donc préférable d'utiliser une expression booléenne qui teste un écart avec une valeur attendue.

```python
def decimetre2metreensharinge):
    """
    Converti en mètres une distance donnée en décimètres
    Paramètre :
        - distance : float une distance en mètres
    Valeur de retour :
        - float - la distance convertie en décimètres
    Contraintes : aucune
    Exemples :
        >>> abs(decimetre2metre(3) - 0.3) < 0.000001
        True
        >>> abs(decimetre2metre(51) - 5.1) < 0.000001
        True
        """
    return distance * 0.1
```

## Tests de fonctions renvoyant des chaînes de caractères

Plusieurs valeurs littérales permettent de désigner une même chaîne de caractères, par exemple suivant que l'on utilise des apostrophes ' ou des guillemets " comme délimiteurs.

On préférera ici aussi utiliser une expression booléenne pour tester l'égalité d'une valeur renvoyée avec une chaîne de caractères donnée.

À cette première version :

```python
def enlumine(chaine):
    """
    Renvoie une chaîne de caractères ornée de quelques enluminures
    Paramètre :
        - chaîne : str - la chaîne à enluminer
    Valeur de retour :
        - str : la chaîne enluminée
    Contraintes : aucune
    Exemples :
        >>> enlumine("bonjour")
        '< bonjour -->>'
        >>> enlumine("j'adore les apostrophes !")
        "<<-- j'adore les apostrophes ! -->>"
        """
    return "<<-- " + chaîne + " -->>"
```

on préfèrera donc

```python
def enlumine(chaine):
    """
    Renvoie une chaîne de caractères ornée de quelques enluminures
    Paramètres :
        - chaîne : str - la chaîne à enluminer
    Valeur de retour :
        - str : la chaîne enluminée
    Contraintes : aucune
    Exemples :
```
>>> enlumine("bonjour") == '<-- bonjour -->>'
True
>>> enlumine("j'adore les apostrophes !") == "<<-- j'adore les apostrophes ! -->>"
True
""
return "<<-- " + chaine + " -->>"

Memento

* les doctests peuvent prendre la forme d'expressions booléennes

* il est préférable de passer par une expression booléenne pour tester une fonction qui renvoie un réel

* il est préférable de passer par une expression booléenne pour tester une fonction qui renvoie une chaîne de caractères
# Expressions booléennes et L1test

## Coup d'œil

On découvre :

* comment L1test compare la valeur attendue et la valeur obtenue d'un test

* l'utilité des expressions booléennes pour écrire certains L1tests

* un moyen de tester les fonctions utilisant l'aléatoire

* un moyen de tester les fonctions renvoyant une valeur flottante

## Expression booléenne et évaluation d'un L1test

On rappelle que dans sa forme la plus simple un l1test s'écrit sur 2 lignes :

```python
$$$$ <expression_à_evaluer>
<expression_résultatAttendu>
```

Pour produire le verdict associé au test, L1test compare la valeur obtenue (par évaluation de `<expression_à_evaluer>` et la valeur attendue (obtenue par évaluation de `<expression_résultatAttendu>`). Le résultat de la comparaison est vrai (True) ou faux (False)<fnref n="4" /> : la comparaison peut s'écrire par une expression booléenne. L1test procède en utilisant l'opérateur de comparaison ==.

## Exemples

### Avec un test correct

Pour évaluer le test suivant :

```python
$$$$ plus_grande_racine(1, 1, -6)
2.0
```

L1test va évaluer l'expression `plus_grande_racine(1, 1, -6)` (valeur obtenue : 2.0), l'expression 2.0 (valeur attendue : 2.0), puis évaluer l'expression booléenne `2.0 == 2.0`. Cette expression vaut True : le test passe.

L'expression `2.0 == 2` valant de même True, le test peut s'écrire aussi :

```python
$$$$ plus_grande_racine(1, 1, -6)
2
```

La valeur attendue peut être décrite par une expression si on estime que la lisibilité du test s'en trouve accrue :

```python
def duplication(chaine, n):
    '''
    Renvoie la chaîne `chaine` répétée `n` fois.
    '''
```

#### Paramètres :

- `chaine` (str) : la chaîne à répéter

- `n` (int) : le nombre de répétitions

- Valeur de retour (str) :

- `CU : n >= 0`

#### Exemples :

<footnote n="4">⁴s'il ne se produit pas une erreur.</footnote>
```python
$$. duplication('oh', 0)
''
$$. duplication('', 4)
''
$$. duplication('oh', 1)
'oh'
$$. duplication('oh', 4)
'oh' + 'oh' + 'oh' + 'oh'
'''
```

NB on peut aussi utiliser les différentes syntaxes des littéraux de type chaîne. À la place de la valeur attendue 'oh' on aurait pu utiliser "oh", car l'expression booléenne 'oh' == "oh" vaut True.

### Avec un test incorrect

Pour évaluer le test suivant :

```python
$$. plus_grande_racine(1, 1, -6)
20
```

L1test va évaluer l'expression `plus_grande_racine(1, 1, -6)` (valeur obtenue : 2.0), l'expression `20` (valeur attendue : 20), puis évaluer l'expression booléenne `2.0 == 20`. Cette expression vaut `False`: le test est en échec.

## Tests de fonctions utilisant l'aléatoire

Quand le résultat d'une fonction dépend d'un calcul qui utilise l'aléatoire, on ne peut pas prévoir ce résultat.

Considérons la fonction `de_6` qui simule et renvoie le lancer d'un dé à 6 faces.

```python
def de_6():
    """
    Renvoie une valeur entière tirée aléatoirement entre 1 et 6.

    Paramètre : aucun
    Valeur de retour (int) : une valeur entière entre 1 et 6
    Contraintes : aucune
    Exemples : CI DESSOUS
    """
    return randomrandint(1,6)
```

Tout ce qu'on peut dire du résultat d'un appel à `de_6`, c'est que la valeur obtenue est comprise entre 1 et 6. C'est la description d'une propriété du résultat. Cette propriété, dans notre cas un encadrement, peut être décrite par une expression booléenne. On écrit ainsi les tests :

```python
$$. 1 <= de_6()
True
$$. de_6() <= 6
True
```

Cette forme de tests ne signifie pas que la fonction `de_6` est un prédicat : la fonction renvoie bien un entier. Elle signifie que, à défaut de pouvoir décrire exactement son résultat, on décrit une propriété booléenne de ce résultat.

NB il doit être bien clair que si on écrit le jeu de tests incorrect suivant :

```python
$$. de_6()
1
$$. de_6()
2
[...]
$$. de_6()
5
$$. de_6()
6
```

alors, chaque test effectuant son propre appel à la fonction `de_6`, peut-être que certains tests passeront, peut-être qu'aucun test ne passera.
Si on souhaite énumérer toutes les valeurs possibles, on peut écrire cette forme particulière de `lltest` :

```python
$$$ v = de_6()
$$$ v == 1 or v == 2 or v == 3 or v == 4 or v == 5 or v == 6
True
```

La première ligne est une affectation : `v` prend la valeur d'un appel à `de_6`. Il n'y a pas de verdict, pas de comparaison effectuée. La seconde ligne est un `lltest` classique qui passe ssi la valeur de `v` est bien comprise entre 1 et 6. Il y aura un seul verdict d'affiché dans la fenêtre de test.

**NB** De même le test suivant est incorrect :

```python
$$$ de_6()
1 or 2 or 3 or 4 or 5 or 6
```

car même si aucune erreur de syntaxe n'est levée, l'expression décrivant la valeur attendue n'est pas une expression booléenne correctement construite (la valeur de l'expression<fnref n="5" /> `1 or 2 or 3 or 4 or 5 or 6` vaut `1`). Comme le précédent, ce test passera ou échouera au gré des exécutions.

## Tests de fonctions renvoyant des réels

Il est impossible de prévoir la valeur exacte d'un réel renvoyé par une fonction à cause des approximations et arrondis. Par exemple :

```python
>>> 3 * 0.1
0.30000000000000004
```

Ainsi, pour tester la fonction suivante :

```python
def decimetre2metreensistance):
    """
    Convertit en mètres une distance donnée en décimètres.

    Paramètre :
        - distance (float): une distance en mètres
        - Valeur de retour (float): la distance convertie en décimètres
        - Contraintes : distance >= 0
        - Exemples : CI DESSOUS
    """
    return distance * 0.1
```

on ne pourra pas écrire que la valeur attendue pour `decimetre2metre(3)` est `0.3`, puisque ce n'est pas le cas.

On pourra écrire le test en utilisant une expression booléenne qui évalue la valeur absolue de l'écart avec la valeur attendue :

```python
$$$ abs(decimetre2metre(3) - 0.3) < 0.000001
True
```

À nouveau, ça ne veut pas dire que `decimetre2metre` est un prédicat : `decimetre2metre` renvoie un flottant. Mais le test décrit une propriété booléenne du résultat renvoyé.

Ou encore on pourra utiliser la fonction Python prédéfinie `isclose`, qui prend en paramètre deux nombres et qui renvoie `True` si ces deux nombres sont proches, avec une tolérance de $10^{-9}$. Un troisième paramètre optionnel peut être ajouté pour préciser la tolérance :

```python
$$$ isclose(decimetre2metre(3), 0.3)
True
$$$
$$$
isclose(decimetre2metre(561), 56, rel_to1=0.01)
True
```

<footnote n="5">⁵La manière dont s'évalue cette expression n'est pas traitée dans le cours.</footnote>
Memento

* les tests peuvent prendre la forme d'expressions booléennes

* il est nécessaire de passer par une expression booléenne pour tester une fonction qui utilise l'aléatoire

* il est possible de passer par une expression booléenne pour tester une fonction qui renvoie un réel
# Exercices niveau débutant

## Expressions booléennes

Pour les questions qui suivent, n'utilisez pas tout de suite l'interpréteur Python.

* Anticipez les valeurs des expressions booléennes (True ou False).

* Vérifiez, ensuite la validité de votre réponse en utilisant l'interpréteur.

## Opérateurs de comparaison

66. `x = 3`
   `12 < x`
   `x == 1+2`
   `5 != x`
   `x + 5. >= x`
   `0.1 == 1/10`
   `"test" == "Test"`
   `"test" == "t" + 'est'`
   `Test ' != Test "`
   `"Test" != " Test"`
   `"1" + "2" == "12"`

## Opérateurs booléens

67. Quelques expressions booléennes à étudier :

```
not True
x = 6
x > 5 and x < 10
x > 5 or x < 10
x = 5
x > 5 and x < 10
x > 5 or x < 10
```

68. En déduire une expression pour vérifier si un nombre appartient à un intervalle, exemple $x \in ]5, 10[$

69. Et une expression qui vérifie si un nombre n'appartient pas à un intervalle, exemple $x \notin ]5, 10[$

## Prédicats

Il s'agit de définir des prédicats (fonctions renvoyant un booléen). Pour chacun :

* on définira quels sont ses paramètres,

* on définira quelles sont les contraintes d'utilisation, s'il y en a,

* on rédigera une docstring complète, y compris des doctests,

* on écrira le code la fonction (pour ces exercices, c'est toujours possible en une seule instruction),

* on validera ce code à l'aide des doctests.

70. Proposez des prédicats pour vérifier :

1. si un nombre passé en paramètre est impair

2. si un nombre passé en paramètre est multiple de 7

3. si une chaîne de caractères est vide (proposez deux codes différents)
4. si un nombre est positif

5. si deux nombres sont tous deux positifs

6. si deux nombres sont de même signe

7. si deux nombres sont de signes opposés

8. si un nombre donné est dans un intervalle donné (bornes comprises).

## États de l'eau

À une pression atmosphérique normale, l'eau est à l'état solide lorsque la température est inférieure à 0°C, à l'état gazeux au delà de 100°C, et à l'état liquide sinon. La température ne peut atteindre la valeur -273.15°C et les valeurs inférieures.

71. Proposez un prédicat est_glace() pour vérifier si l'eau est sous forme de glace à une température donnée en paramètre.

72. Proposez un prédicat est_liquide() pour vérifier si l'eau est liquide à une température donnée.

## Tolérance pour les flottants

Les approximations de la représentation des flottants empêchent l'utilisation de l'égalité :

```python
>>> 0.3 == 3 * 0.1
True
```

False

L'égalité sur les flottants est donc avantageusement remplacée par une comparaison à $\epsilon$ près.

73. Proposez un prédicat qui vérifie si deux valeurs flottantes sont proches. En sus des deux valeurs, le prédicat acceptera un troisième paramètre représentant la tolérance acceptable.

Remarque : Le module math de la bibliothèque Python fournit un prédicat isclose() qui vérifie si deux valeurs sont proches l'une de l'autre ou non, prédicat que l'on utilisera pas ici !

## Tests de fonctions

Reprenez certaines des fonctions codées dans les exercices sur les fonctions et réécrivez leurs doctests en utilisant une expression booléenne.

Attention,

* pour les fonctions qui renvoient un nombre à virgule flottante, on prendra en compte que cette valeur est une approximation,

* pour les fonctions qui renvoient une valeur aléatoire, on vérifiera par exemple que la valeur est dans un intervalle donné.

Les fonctions à modifier sont :

1. celsius2fahrenheit()

2. cercle_circonference()

3. de_6()

4. de(nfaces)

5. enluminure1()
# Exercices niveau intermédiaire

## Prédicats

Pour chacun des prédicats (fonctions renvoyant un booléen),

* on définira quels sont ses paramètres,

* on définira quelles sont les contraintes d'utilisation, s'il y en a,

* on rédigera une docstring complète, y compris des doctests,

* on écrira le code la fonction (pour ces exercices, c'est toujours possible en une seule instruction),

* on validera ce code à l'aide des doctests.

Lorsque c'est possible, vous utiliserez les prédicats définis dans les exercices précédents (y compris ceux du niveau débutant) pour écrire le code des nouveaux prédicats.

## Intervalles

Étant donnés deux intervalles passés en paramètres, écrivez un prédicat pour vérifier si

74. leur intersection est vide

75. le second est inclus dans le premier

76. ils se recouvrent (ie le premier inclus dans le second ou inversement)

## Quatre vingt-et-un

L'objectif est de déterminer si un lancer de trois dés six (dés à 6 faces) forme un 421. On procédera par étapes.

77. Proposez un prédicat qui renvoie vrai si et seulement si (ssi) deux dés forment un 42, c'est-à-dire que l'un des dés est un 2, l'autre un 4.

def est_un_42(de1, de2):
"""

Renvoie vrai si et seulement si deux dés forment un 42

Paramètres :

- de1 : entier - un lancer de dé six

- de2 : entier - un autre lancer de dé six

Valeur de retour : booléen

Contraintes : de1 et de2 appartiennent à [1, 6]

:Exemples:

>>> est_un_42(4, 2)

True

>>> est_un_42(2, 4)

True

>>> est_un_42(4, 4)

False

>>> est_un_42(5, 1)

False

"""

78. Proposez un prédicat qui renvoie vrai si et seulement trois dés forment un 421. On utilisera le prédicat précédent.

79. Proposez maintenant un prédicat qui n'utilise pas le prédicat qui teste le 42. Il énumérera donc les différentes combinaisons possibles.
## En automne

80. Considérant que l'automne débute le 22 septembre et termine le 20 décembre, proposez un prédicat qui renvoie vrai si et seulement si une date donnée est en automne. Complétez :

```python
def en_automne(jour, mois):
    """Renvoie vrai si et seulement si une date donnée est en automne.

    Paramètres :
    - jour (int) : numéro du jour
    - mois (int) : numéro du mois

    Valeur de retour :
    - bool

    Contraintes :
    - 1 <= mois <= 12 et 1 <= jour <= njour
        njour étant le nombre de jours du mois (31 pour janvier...)

    Exemples :
    >>> en_automne(1, 1)
    False
    >>> en_automne(21, 9)
    False
    >>> en_automne (22, 9)
    True
    """
```

## Au cinéma

Au cinéma, le tarif réduit est accordé :

* aux mineurs - moins de 18 ans

* aux seniors - plus de 60 ans

Nous désirons savoir si - étant donnée sa date de naissance - une personne peut bénéficier du tarif réduit.

Nous allons procéder par étapes.

81. Proposez un prédicat pour comparer deux dates fournies sous la forme de 3 entiers yyyy, mm, jj. Ce prédicat acceptera donc 6 paramètres et renverra True quand la première date est antérieure à la seconde.

On suppose la date du jour représentée par trois constantes entières, connues des prédicats. Par exemple

```
TODAY YEAR = 2020
TODAY_MONTH = 4
TODAY_DAY = 1
```

82. Proposez un prédicat qui indique si, étant donné sa date de naissance, une personne est senior.

83. Proposez un prédicat qui indique si, étant donné sa date de naissance, une personne est mineure.

De plus, le tarif réduit est également accordé :

* aux étudiants de moins de 26 ans

84. Proposez un nouveau prédicat pour déterminer si un client ou une cliente bénéficie du tarif réduit. Ce prédicat prend en paramètre une date de naissance et un booléen qui indique si la personne est étudiante.

## Autour du calendrier

Une première fonction de manipulation de dates et de calendriers que nous compléterons par la suite.

### Année bissextile

L'année compte habituellement 365 jours. Les années bissextiles comptent 366 jours.

Depuis l'ajustement du calendrier grégorien, l'année est bissextile :

* si l'année est divisible par 4 et non divisible par 100, ou

* si l'année est divisible par 400.
Ainsi, 2019 n'était pas bissextile.

L'année 2020 est bissextile suivant la première règle : divisible par 4 et non divisible par 100.

L'an 1900 n'était pas bissextile car divisible par 4 mais aussi par 100, et non divisible par 400.

L'an 2000 était bissextile car divisible par 400.

(Source Wikipédia fr.wikipedia.org/wiki/Année_bissextile)

85. On pourra définir un prédicat préliminaire de test de divisibilité :

```python
def est_divisible_par(a, b):
    """
    Test de divisibilité de deux entiers.
    Paramètres :
        a, b : deux entiers
        Valeur de retour : booléen
        - True si a est divisible par b
        - False sinon
    Contraintes :
        - b non nul
    Exemples :
        >>> est_divisible_par(10, 2)
        True
        >>> est_divisible_par(10, 3)
        False
        """
```

86. Définissez un prédicat est_bissextile() :

```python
def est_bissextile(annee):
    """
    Paramètres :
        annee : entier - l'année à tester
        Valeur de retour : booléen
        - True si annee est une année bissextile
        - False sinon
    Contraintes :
        - annee > 1582, année d'établissement du calendrier grégorien
    Exemples :
        >>> est_bissextile(2019)
        False
        >>> est_bissextile(2020)
        True
        >>> est_bissextile(1900)
        False
        >>> est_bissextile(2000)
        True
        """
```
# Exercices niveau confirmé

## Ou exclusif - xor

Le *ou exclusif* - noté xor - est un autre opérateur booléen. Il permet d'exprimer le fait qu'une et une seule valeur parmi deux est vraie : x xor y est vrai si soit x est vrai, soit y est vrai, mais pas les deux ensemble. La table de vérité de l'opérateur *ou exclusif* est donc la suivante

<table><thead><tr><th>x</th><th>y</th><th>x xor y</th></tr></thead><tbody><tr><td>False</td><td>False</td><td>False</td></tr><tr><td>False</td><td>True</td><td>True</td></tr><tr><td>True</td><td>False</td><td>True</td></tr><tr><td>True</td><td>True</td><td>False</td></tr></tbody></table>

87. Observez les couples de valeurs de x et de y pour lesquels x xor y est vrai.

88. Écrivez le prédicat xor() qui prend en paramètre deux booléens et qui renvoie True si l'un ou l'autre des paramètres est True.

Vous devez écrire tous les tests unitaires pour ce prédicat puisqu'il est possible d'énumérer toutes les paires possibles de paramètres.

## Intersection et recouvrement de rectangles

La figure ci-dessous illustre un rectangle défini par deux points (xmin, ymin) et (xmax, ymax) dans un repère orthonormé.

<figure><img src="image_5.png" /><figcaption>Figure 2: Rectangle dans un repère orthonormé</figcaption></figure>

89. Écrivez un prédicat qui prend en paramètre les coordonnées de ces 2 points et celles d'un troisième point et qui renvoie True si le troisième point est inclus dans le rectangle.

90. Écrivez un prédicat qui vérifie si le premier rectangle passé en paramètre est inclus dans le second.

91. Écrivez un prédicat qui vérifie si deux rectangles ont une intersection vide.

## Les lois de De Morgan

Les descriptions proviennent de la page Wikipédia sur les Lois de De Morgan.

Les lois de De Morgan sont des identités entre propositions logiques. Elles ont été formulées par le mathématicien britannique Augustus De Morgan (1806-1871).
**1re loi** — **négation de la disjonction** La négation de la disjonction de deux propositions est équivalente à la conjonction des négations des deux propositions.
Cela signifie que « not(A or B) » est équivalent à « not(A) and not(B) ».

**2nde loi** — **négation de la conjonction** La négation de la conjonction de deux propositions est équivalente à la disjonction des négations des deux propositions.
Cela signifie que « not(A and B) » est équivalent à « not(A) or not(B) ».

Nous allons écrire une série de prédicats afin de démontrer ces lois.

92. Définissez une fonction `de_morgan1()` qui accepte deux valeurs booléennes en paramètre et renvoie le résultat de la comparaison entre les deux membres de l'équivalence de la 1re loi pour ces valeurs.

93. La première loi de De Morgan est vraie si la fonction `de_morgan1()` renvoie vrai pour toute combinaison de paramètres.

Définissez une fonction `loi_de_de_morgan1()` qui renvoie vrai si et seulement si chacun des appels possibles à `de_morgan1()` renvoie vrai :

* remarquez que la fonction `de_morgan1()` acceptant deux paramètres, chacun des paramètres pouvant prendre deux valeurs, il existe quatre combinaisons possibles des paramètres.

* la fonction `loi_de_de_morgan1()` n'attend pas de paramètre. Si un appel de la fonction renvoie vrai, la 1re loi de De Morgan est prouvée.
La documentation de la fonction comportera donc un unique doctest :

```
>>> loi_de_de_morgan1()
True
```

94. Définissez maintenant deux fonctions `de_morgan2()` et `loi_de_de_morgan2()` pour vérifier la seconde loi de De Morgan.

## Complétude du non-et

### Opérateur non-et

Le non-et ou `nand` est un autre opérateur booléen défini par la table de vérité suivante :

<table><thead><tr><th>x</th><th>y</th><th>x nand y</th></tr></thead><tbody><tr><td>False</td><td>False</td><td>True</td></tr><tr><td>False</td><td>True</td><td>True</td></tr><tr><td>True</td><td>False</td><td>True</td></tr><tr><td>True</td><td>True</td><td>False</td></tr></tbody></table>

L'expression `x nand y` est vraie si et seulement si au moins un des deux opérandes est faux.

95. Proposez une fonction `nand()` correspondant à cet opérateur.

## Complétude du non-et

L'opérateur `nand` est un opérateur complet. Cela signifie que toute expression de l'algèbre de Boole peut être écrite uniquement à l'aide `nand`.

Afin de confirmer cette propriété, nous allons définir des fonctions pour écrire les trois opérateurs `not`, `and`, et `or` à l'aide de l'unique `nand`.

96. Opérateur `not`

1. Quelle est la valeur de `nand(True, True)` ?
De `nand(False, False)` ?

2. En déduire une expression équivalente à « not x » n'utilisant que l'opérateur `nand`.

3. Proposez une fonction `not_with_nand()` équivalente au `not` écrite à base de seuls appels à `nand()`.

97. Opérateur `and`

1. Comparez les tables de vérités des opérateurs `and` et `nand`.

2. En déduire une expression équivalente à « x and y » n'utilisant que l'opérateur `nand`.

3. Proposez une fonction `and_with_nand()` équivalente au `and` écrite à base de seuls appels à `nand()`.
98. Opérateur *or*

1. Proposez une expression équivalente à *x* or *y* » n'utilisant que l'opérateur *nand*.

2. Proposez une fonction `or_with_nand()` équivalente au `or` écrite à base de seuls appels à `nand()`.

99. Proposez enfin un prédicat `verification_completeness_nand()` qui renvoie vrai si et seulement si l'ensemble des comparaisons suivantes sont vraies :

* `not_with_nand(a) == not a` pour toutes les valeurs de *a* possibles

* `and_with_nand(a, b) == a and b` pour tous les couples de valeurs de *a* et *b* possibles

* `or_with_nand(a, b) == a or b` pour tous les couples de valeurs de *a* et *b* possibles

## Porte logique NAND

Les opérations arithmétiques exécutées par les processeurs sont réalisées à base de circuits. Ces circuits sont construits à base de portes logiques. Une porte logique étant une réalisation matérielle – électronique – d'un opérateur logique.

La complétude de l'opérateur *nand* fait qu'il est possible de construire un processeur à l'aide de seules portes logiques NAND.

De plus, la porte logique NAND est particulièrement facile à construire, les processeurs courants sont effective-ment construits à base de cette unique porte.
4. Instruction conditionnelle
# Instruction conditionnelle

## Coup d'œil

On découvre

* comment exécuter certaines parties d'un programme dans certaines situations

* ce qu'est une instruction conditionnelle

## Instruction conditionnelle

Dans certaines situations, on peut vouloir n'exécuter une instruction – ou une suite d'instructions – que si une condition est vérifiée.

Par exemple, corriger le nombre de jours du mois de février dans le cas où l'année est bissextile. Autre exemple, ne mener le calcul des racines d'une équation du second degré que si le déterminant est positif.

Il nous faut pourvoir identifier

* la condition : ce sera une valeur booléenne

* la ou les instructions à exécuter : ce sera un bloc d'instructions, repéré par une indentation supplémentaire

## Instruction if

En Python, on utilisera le mot clé `if` et on écrira par exemple :

```python
njours = 28
if est_bissextile(annee):
    njours = 29
```

ou

```python
if delta >= 0:
    racine1 = (-b + sqrt delta) / (2 * a)
    racine2 = (-b - sqrt delta) / (2 * a)
```

Notez le caractère : qui termine la première ligne et introduit le bloc d'instructions.

La fin du bloc de code est marquée par la reprise de l'indentation précédente. Et donc par exemple, si notre calcul du nombre de jours était suivi d'une instruction d'affectation pour le calcul du nombre d'heures, on pourrait écrire

```python
njours = 28
if est_bissextile(annee):
    njours = 29
nheures = 24 * njours
```

## Flot d'exécution

Le flot d'exécution d'un programme est l'ordre dans lequel les instructions sont exécutées.

Ce flot d'exécution suit les instructions d'une séquence les unes après les autres.

(Note à propos de ce flux d'exécution : nous avons vu qu'un appel de fonction permet 1.- d'interrompre la séquence pour exécuter les instructions de la fonction et, 2.- de reprendre ensuite l'exécution là où elle avait été laissée.)
Plutôt que d'exécuter les unes après les autres les instructions d'un programme, l'instruction conditionnelle `if` permet de potentiellement modifier ce flot d'exécution.

Considérons le code suivant :

```python
...
if condition :
    instructions
...
```

Le flot d'exécution correspondant peut être illustré par le graphe :

<figure><img src="image_6.png" /></figure>

Dans l'ordre :

* la condition est évaluée. On obtient une valeur booléenne.

* si cette valeur est `vrai`

- les instructions associées au `if` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...).

* si cette valeur est `faux`

- on passe directement à l'exécution des instructions suivantes (...).

## Imbrications

Le bloc d'instructions d'un `if` peut lui-même comporter d'autres instructions conditionnelles.

Une instruction `if` peut elle-même être placée dans un autre bloc, par exemple le bloc d'instructions d'une fonction.

Nous avons alors à faire avec des blocs d'instructions *imbriqués*.

La manière dont les instructions sont indentées contrôle cette imbrication. Il faut y être très attentif.

Dans le code suivant :

```python
if mois == "février" :
    njours = 28
    if est_bissextile(annee) :
        njours = 29
        nheures = 24 * njours
```

l'instruction de calcul de `nheures` fait partie du bloc d'instructions du `if mois == "février"`, elle ne sera donc exécutée que si le mois est février.

Le code suivant diffère très peu du précédent :

```python
if mois == "février" :
    njours = 28
    if est_bissextile(annee) :
        njours = 29
        nheures = 24 * njours
```

L'instruction de calcul du nombre d'heures sera ici exécutée, que le mois soit février ou pas.
Observons aussi l'indentation des instructions de cette fonction (la docstring est abrégée pour les besoins de la présentation) :

```python
def nombre_heures(mois, annee):
    """Renvoie le nombre d'heures d'un mois donné du 1er trimestre."""
    if mois == "janvier" or mois == "mars" :
        njours = 31
    if mois == "février" :
        njours = 28
    if est_bissextile(annee) :
        njours = 29
    nheures = 24 * njours
    return nheures
```

Le code la fonction contient quatre instructions :

* lignes 3-4 : une première instruction conditionnelle, `if mois == "janvier" or ...`

* lignes 5-8 : une seconde instruction conditionnelle, `if mois == "février"`

Le bloc d'instructions de cette conditionnelle est lui-même formé de deux instructions. La seconde étant une instruction conditionnelle.

* ligne 9 : l'instruction de calcul de nheures

* ligne 10 : l'instruction de terminaison de la fonction.

Cette lecture de la structure du code se fait sur la base de l'indentation des instructions.

## Memento

* l'instruction conditionnelle `if` permet de n'exécuter un bloc d'instructions que si une condition est vraie

* les instructions conditionnelles peuvent être imbriquées

* l'indentation définit la manière dont les instructions seront exécutées. Le soin à apporer à cette indentation est capital.
# Instruction conditionnelle et alternative

## Coup d'œil

On découvre :

* comment exécuter certaines parties d'un programme dans certaines situations, et d'autres parties du programme dans les autres situations

* qu'une alternative peut être ajoutée à une instruction conditionnelle

## Alternative

L'instruction conditionnelle `if` permet d'exécuter un bloc d'instructions si et seulement si une condition est vraie.

Il existe d'autres situations où l'on peut vouloir exécuter un bloc d'instructions si une condition est vérifiée, et exécuter un autre bloc d'instructions si cette même condition n'est pas vérifiée.

Par exemple, définir le nombre de jours du mois de février à 29 dans le cas où l'année est bissextile, et définir ce nombre de jours à 28 dans les autres cas.

Ou par exemple mener le calcul des racines d'une équation du second degrés d'une certaine manière dans le cas où le coefficient $a$ est nul, et d'une autre manière dans les autres cas.

Il nous faut pouvoir identifier :

* la condition : ce sera une valeur booléenne

* la ou les instructions à exécuter quand la condition est vraie : ce sera un bloc d'instructions

* la ou les instructions à exécuter quand la condition n'est pas vraie : ce sera un autre bloc d'instructions

## Mot-clé `else`

En Python, on utilisera le mot clé `else`, en association au mot clé `if` que nous connaissons. On écrira par exemple :

```python
if est_bissextile(annee):
    njours = 29
else:
    njours = 28
```

Notez le caractère : qui suit le `else` et introduit le bloc d'instructions à exécuter dans le cas où la condition n'est pas vraie.

Notez aussi que la ligne comportant le mot clé `else` est indentée au même niveau que celle du mot clé `if`.

Comme toujours, la fin des blocs de code est marquée par la reprise de l'indentation précédente. Et donc par exemple, si notre calcul du nombre de jours était suivi d'une instruction d'affectation pour le calcul du nombre d'heures, on pourrait écrire :

```python
if est_bissextile(annee):
    njours = 29
else:
    njours = 28
```
```
njours = 28
nheures = 24 * njours
```

## Flot d'exécution

La construction `if ... else` permet donc de diriger le flot d'exécution vers un bloc d'instructions ou un autre, en fonction d'une condition.

Le flot d'exécution correspondant au code suivant :

```
...
if condition :
    instructions si
else :
    instructions sinon
...
```

peut être illustré par :

<figure><img src="image_7.png" /></figure>

L'exécution consistera, dans l'ordre

* la condition est évaluée. On obtient une valeur booléenne.

* si cette valeur est *vrai*

- les instructions associées au `if` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...)

* si cette valeur est *faux*

- les instructions associées au `else` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...)

Notre exemple

```
if est_bissextile(annee) :
    njours = 29
else :
    njours = 28
```

peut être traduit en

```
Si annee est bissextile
    on affecte la valeur 29 à njours
Sinon, c'est-à-dire si annee n'est pas bissextile
    on affecte la valeur 28 à njours
```

## Imbrication

Les blocs d'instructions conditionnels et alternatifs peuvent eux-mêmes contenir des instructions conditionnelles :

```
# nombre de jours et d'heures d'un mois donné du 1er trimestre
if mois == "janvier" or mois == "mars" :
    njours = 31
```
```python
else: # février
    if est_bissextile(annee):
        njours = 29
    else:
        njours = 28
nheures = 24 * njours
```

Bien évidement, la bonne indentation des instructions est essentielle. Comparez les deux codes :

```python
# nombre de jours et d'heures d'un mois donné du 1er trimestre
if mois == "février":
    njours = 28
    if est_bissextile(annee):
        njours = 29
else: # janvier ou mars
    njours = 31
nheures = 24 * njours
```

et

```python
# nombre de jours et d'heures d'un mois donné du 1er trimestre
# version erronée
if mois == "février":
    njours = 28
    if est_bissextile(annee):
        njours = 29
else: # janvier ou mars
    njours = 31
nheures = 24 * njours
```

Dans ce dernier exemple, le `else` de la ligne 7 se rapporte au `if` de la ligne 5, et ne correspond donc pas aux mois de janvier ou mars, mais aux mois de février des années non bissextiles...

## Memento

* l'alternative `else` permet de n'exécuter un bloc d'instructions que si la condition d'un instruction conditionnelle `if` est fausse
# Instruction conditionnelle - Alternatives multiples

## Coup d'œil

On découvre :

* comment exécuter certaines parties d'un programme dans différentes situations

* que plusieurs alternatives peuvent être énumérées dans une instruction conditionnelle

## Énumération d'alternatives

Nous avons vu que :

* l'instruction conditionnelle `if` permet d'exécuter un bloc d'instructions si et seulement si une condition est vraie,

* l'alternative `else` permet d'exécuter un autre bloc d'instructions si cette condition est n'est pas vraie.

Il existe d'autres situations où l'on peut vouloir exécuter d'autres blocs d'instructions si d'autres conditions sont vérifiées.

Nous sommes face à une série de situations, et pour chacune d'elles voulons exécuter des instructions particulières.

Par exemple, définir le nombre de jours d'un mois du premier semestre qui peut être 31, 30, 28 ou 29 jours.

Il nous faut pouvoir identifier

* une première condition : ce sera une valeur booléenne

* la ou les instructions à exécuter quand cette condition est vraie : ce sera un bloc d'instructions

* une deuxième condition, toujours une valeur booléenne

* la ou les instructions à exécuter quand cette condition est vraie : ce sera un autre bloc d'instructions

* ... autant d'autres conditions que nécessaire

* ... autant d'autres blocs d'instructions associés à ces conditions

* éventuellement la ou les instructions à exécuter quand aucune de ces conditions n'est vraie : ce sera un dernier bloc d'instructions

## Mot-clé elif

En Python, on utilisera le mot clé `elif` (pour `else if`), en association au mot clé `if` que nous connaissons. On écrira par exemple :

```python
if mois == "janvier" or mois == "mars" or mois == "mai":
    njours = 31
elif mois == "avril" or mois == "juin":
    njours = 30
else:  # février
    if est_bissextile(annee):
        njours = 29
    else:
        njours = 28
nheures = 24 * njours
```
Notez la construction identique du `if` et du `elif` avec une condition suivie du caractère `:` pour introduire un bloc d'instructions à exécuter dans le cas où la condition est vraie.

Notez aussi que les lignes comportant les mot-clés `if`, `elif` et `else` sont indentées au même niveau.

Comme toujours, la fin des blocs de code est marquée par la reprise de l'indentation précédente.

## Flot d'exécution

La construction `if ... elif ... else` permet donc de diriger le flot d'exécution vers un bloc d'instructions ou un autre, en fonction de différentes conditions.

Notez bien que le flot d'exécution passera par un et un seul des blocs d'instructions.

Le flot d'exécution correspondant au code suivant :

```python
...
if condition :
    bloc d'instructions si
elif condition sinon :
    bloc d'instructions sinon si
else :
    bloc d'instructions sinon sinon
...
```

peut être illustré par :

<figure><img src="image_8.png" /></figure>

L'exécution consistera, dans l'ordre

* la première condition est évaluée. On obtient une valeur booléenne.

* si cette valeur est `vrai`

- les instructions associées au `if` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...)

* si cette valeur est `faux`, la deuxième condition est évaluée. On obtient une nouvelle valeur booléenne.

* si cette nouvelle valeur est `vrai`

- les instructions associées au `elif` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...)

* les conditions suivantes sont évaluées tant que l'une d'elles n'est pas `vrai`. Si aucune d'elles n'est `vrai`

- les instructions associées au `else` sont exécutées

- avant de poursuivre l'exécution des instructions suivantes (...).

## Ordre des conditions

Un seul des blocs d'instructions est exécuté, celui qui correspond à la première condition évaluée à `vrai`. L'ordre dans lequel les différentes conditions sont énumérées est donc important.

Soit l'exemple suivant qui permet de savoir dans quelle catégorie est un ou une sportive en tir à l'arc :

```python
if age <= 10:
    categorie = "poussin"
if 10 < age and age <= 12:
    categorie = "benjamin"
if 12 < age and age <= 14:
```
```
categorie = "minime"
if 14 < age and age <= 17:
    categorie = "cadet"
if 17 < age and age <= 20:
    categorie = "junior"
else:
    categorie = "senior"
```

L'utilisation d'une construction `elif` avec un ordre des conditions bien choisi peut permettre de simplifier les conditions :

```
if age <= 10:
    categorie = "poussin"
elif age <= 12:
    categorie = "benjamin"
elif age <= 14:
    categorie = "minime"
elif age <= 17:
    categorie = "cadet"
elif age <= 20:
    categorie = "junior"
else:
    categorie = "senior"
```

Attention, un changement dans l'ordre de ces conditions ne permettra plus d'avoir la bonne catégorie en fonction de l'âge.

## Memento

* la construction `if ... elif ... else` permet d'exécuter différents blocs d'instructions en fonction de différentes conditions

* les conditions d'une construction `if ... elif ... else` sont énumérées tant que l'une d'elles n'est pas vraie

* un seul des blocs d'instructions d'une construction `if ... elif ... else` est exécuté
# Exercices niveau débutant

## Premières fonctions

### Minimum, maximum et valeur absolue

100. Définissez une fonction maximum() prenant en paramètre deux nombres et renvoyant le plus grand de ces deux nombres.
On n'utilisera bien entendu pas la fonction Python prédéfinie max().

101. Définissez une fonction minimum() prenant en paramètre deux nombres et renvoyant le plus petit de ces deux nombres.
On n'utilisera bien entendu pas la fonction Python prédéfinie min().

102. Définissez une fonction valeur_absolue() prenant en paramètre un nombre et renvoyant la valeur absolue de ce nombre.
On n'utilisera bien entendu pas la fonction Python prédéfinie abs().

### Pair et signe

103. Définissez une fonction parite() prenant en paramètre un nombre entier et renvoyant

* la chaîne de caractères 'pair' si l'entier est pair

* la chaîne de caractères 'impair' si l'entier est impair

104. Définissez une fonction signe() prenant en paramètre un nombre et renvoyant une chaîne de caractères :

* 'positif' si le nombre est strictement positif

* 'négatif' si le nombre est strictement négatif

* 'nul' si le nombre est nul

### Pile ou face

105. Définissez une fonction pile_ou Face() simulant le lancer d'une pièce de monnaie non biaisée, et renvoyant aléatoirement la chaîne de caractères 'pile' ou la chaîne de caractères 'face'.
On utilisera une fonction du module random de Python pour simuler l'aléatoire.

## Flots d'exécution

Au tir à l'arc, les catégories sont les suivantes :

* Poussins : 10 ans et moins

* Benjamins : 11 à 12 ans

* Minimes : 13 à 14 ans

* Cadets : 15 à 17 ans

* Juniors : 18 à 20 ans

* Seniors : 21 ans et plus

Ci-dessous différentes fonctions contiennent des instructions conditionnelles afin d'essayer de déterminer la catégorie d'un ou une sportive en fonction de son âge. Anticipez les valeurs renvoyées par ces fonctions dans les cas où le ou la sportive a 10 ans, a 15 ans, a 23 ans.

106. Quelles est ou quelles sont la ou les fonctions qui donne(nt) les résultats attendus ?
Pourquoi les autres ne fonctionnent pas ?
a. Une suite de `if`

```python
def categorie_tir_arc_v1(age):
    if age <= 10:
        categorie = "poussin"
    if age <= 12:
        categorie = "benjamin"
    if age <= 14:
        categorie = "minime"
    if age <= 17:
        categorie = "cadet"
    if age <= 20:
        categorie = "junior"
else:
    categorie = "senior"
return categorie
```

b. Des `elif` à la place des `if`

```python
def categorie_tir_arc_v2(age):
    if age <= 10:
        categorie = "poussin"
    elif age <= 12:
        categorie = "benjamin"
    elif age <= 14:
        categorie = "minime"
    elif age <= 17:
        categorie = "cadet"
    elif age <= 20:
        categorie = "junior"
else:
    categorie = "senior"
return categorie
```

c. Changement dans l'ordre des `elif`

```python
def categorie_tir_arc_v3(age):
    if age <= 10:
        categorie = "poussin"
    elif age <= 20:
        categorie = "junior"
    elif age <= 14:
        categorie = "minime"
    elif age <= 17:
        categorie = "cadet"
    elif age <= 12:
        categorie = "benjamin"
else:
    categorie = "senior"
return categorie
```

d. Changement des conditions et de l'ordre des `elif`

```python
def categorie_tir_arc_v4(age):
    if age <= 10:
        categorie = "poussin"
    elif age <= 12:
        categorie = "benjamin"
    elif age <= 14:
        categorie = "minime"
    elif age > 20:
        categorie = "senior"
    elif age > 17:
        categorie = "junior"
```
```python
else:
    categorie = "cadet"
return categorie
```

## Pour aller plus loin

### Min, max, médiane à trois

107. Définissez deux fonctions maximum3() et minimum3() prenant en paramètre trois nombres et renvoyant respectivement le plus grand et le plus petit de ces trois nombres.
On n'utilisera bien entendu pas les fonctions Python prédéfinies max() ou min().

1. Proposez éventuellement une version utilisant vos fonctions maximum() et minimum() définies précédemment.

2. Proposez une version n'utilisant aucune autre fonction.

108. Définissez une fonction mediane() qui accepte trois nombres en paramètre et renvoie le nombre médian.
# Exercices niveau intermédiaire

## Saison

Définissez une fonction qui renvoie une des chaînes de caractères 'été', 'automne', 'hiver', 'printemps' en fonction de la saison d'une date donnée par son numéro de jour et de mois. On supposera les débuts des saisons suivants :

* printemps le 20 mars

* été le 21 juin

* automne le 22 septembre

* hiver le 21 décembre

## Dans l'ordre

109. Proposez une fonction tri2 qui renvoie une chaîne de caractères présentant *dans l'ordre*, comme sur l'exemple suivant, deux nombres fournis en paramètre :

```python
>>> tri2(1, 3)
'1 ≤ 3'
>>> tri2(3, 1)
'1 ≤ 3'
```

On pourra s'inspirer du code ci-dessous pour créer la chaîne de caractères :

```python
>>> a = 1
>>> b = 3
>>> str(a) + " ≤ " + str(b)
'1 ≤ 3'
```

110. Proposez maintenant une fonction tri3() acceptant deux nombres en paramètres :

```python
>>> tri3(8, 12, 7)
'7 ≤ 8 ≤ 12'
>>> tri3(8, 7, 12)
'7 ≤ 8 ≤ 12'
>>> tri3(12, 8, 7)
'7 ≤ 8 ≤ 12'
```

111. Combien de comparaisons sont effectuées pour trier les trois valeurs ?

Il est possible de proposer une méthode en trois comparaisons.

112. Proposez maintenant de trier quatre valeurs.

## Retour sur le 421

Nous avions défini un prédicat pour déterminer si un lancer de trois dés forme un 421 à l'aide d'expressions booléennes – cf. « Expressions booléennes – niveau intermédiaire ».

On propose ici des fonctions alternatives pour ce même problème.

113. Proposez un prédicat à base d'instructions conditionnelles (et alternatives) imbriquées pour déterminer si trois dés forment un 421.

En supposant que les trois dés sont ordonnés, c'est-à-dire que la valeur du premier dé est supérieure ou égale à la valeur du second qui est elle-même supérieure à la valeur du troisième, permet de simplifier le test de savoir si les 3 dés forment un 421.
114. Proposez une version de `est_un_421()` suivant ce principe. La fonction commencera donc par trier les trois dés pour terminer par un test de la combinaison.

## Impression à tarif dégressif

Une imprimerie applique le tarif suivant pour l'impression de flyers :

* 0,30€ l'unité pour 100 exemplaires

* 0,14€ l'unité pour 250 exemplaires

* 0,09€ l'unité pour 500 exemplaires

* 0,06€ l'unité pour 750 exemplaires

* 0,05€ l'unité pour 1000 exemplaires

* 0,03€ l'unité pour 2000 exemplaires

* 0,02€ l'unité à partir de 3000 exemplaires

Il n'est pas possible de commander d'autres quantités que celles listées, ou des multiples de 1000 au delà de 3000.

115. Proposez une fonction `nombre_exemplaires()` qui indique combien d'exemplaires doivent être commandés pour un nombre de flyers voulus.
Par exemple pour 400 flyers, il sera nécessaire d'en commander 500.

116. Proposez une fonction `montant_facture()` qui donne le prix à payer pour un nombre de flyers voulus.
Par exemple pour 400 flyers voulus, il est nécessaire d'en commander 500 pour un montant de 500 × 0,09€ = 45€.

## Autour du calendrier

Nous complétons notre série de fonctions de manipulation de dates et de calendriers débutée avec le prédicat `est_bissextile()` et rappelé ci-dessous. Ce travail sera poursuivi lors de prochaines séances.

### Année bissextile

Rappel de la feuille d'exercices « Expressions booléennes — niveau intermédiaire ».

L'année compte habituellement 365 jours. Les années bissextiles comptent 366 jours.

Depuis l'ajustement du calendrier grégorien, l'année est bissextile :

* si l'année est divisible par 4 et non divisible par 100, ou

* si l'année est divisible par 400.

Ainsi, 2019 n'était pas bissextile.

L'année 2020 est bissextile suivant la première règle : divisible par 4 et non divisible par 100.

L'an 1900 n'était pas bissextile car divisible par 4 mais aussi par 100, et non divisible par 400.

L'an 2000 était bissextile car divisible par 400.

(source Wikipédia fr.wikipedia.org/wiki/Année_bissextile).

117. Définissez un prédicat `est_bissextile()` :

```python
def est_bissextile(annee):
    """
    Paramètres :
        - annee : entier - l'année à tester
        - Valeur de retour : booléen
            - True si annee est une année bissextile
            - False sinon
    Contraintes :
        - annee > 1582, année d'établissement du calendrier grégorien
    Exemples :
        >>> est_bissextile(2019)
        False
        >>> est_bissextile(2020)
        True
```
>>> est_bissextile(1900)
False
>>> est_bissextile(2000)
True
"""

## Validité d'une date

Il est classique de représenter une date par trois nombres entiers (j, m, a), a désignant l'année, m le mois et j le numéro du jour dans le mois.

Mais bien entendu, tout triplet d'entiers ne représente pas nécessairement une date valide :

* (4, -1, 2020), (4, 15, 2020) ne sont pas des dates valides parce que les entiers -1 et 15 ne désignent pas des mois

* (-2, 1, 2020), (32, 1, 2020), (29, 2, 2018) ne sont pas des dates valides parce que les entiers -2, 32 et 29 ne désignent pas des jours valides pour les mois correspondant

Nous allons définir deux prédicats préliminaires pour déterminer

* la validité d'un numéro de mois

* la validité d'un numéro de jour dans un mois donné d'une année donnée.

## Validité d'un numéro de mois

118. Définissez un prédicat `est_mois_valide()` :

def est_mois_valide(m):
"""
Test de validité d'un numéro de mois
Paramètre :
- m : entier
Valeur de retour : booléen
- True si m représente un mois valide, ie est compris entre 1 et 12
- False sinon
Contraintes : aucune
Exemples :
>>> est_mois_valide(-1)
False
>>> est_mois_valide(15)
False
>>> import random
>>> est_mois_valide(randomeasyrandint(1, 12))
True
"""

## Validité d'un numéro de jour dans un mois

Déterminer la validité d'un numéro de jours dans un mois donné d'une année donnée nécessite de connaître le nombre de jours du mois concerné.

119. Définissez une fonction `nombre_jours()` :

def nombre_jours(mois, annee):
"""
Nombre de jours d'un mois donné d'une année donnée
Paramètres :
- mois, annee : entiers - le mois et l'année
Valeur de retour :
- entier - le nombre de jours dans le mois mois de l'année annee.
Contrainte :
- mois est un mois valide
Exemples :
>>> nombre_jours(1, 2020)
31
>>> nombre_jours(2, 2018)

28

>>> nombre_jours(2, 2020)

29

"""

120. À l'aide des fonctions précédentes, définissez maintenant une fonction est_jour_valide()

def est_jour_valide(j, mois, annee):

"""

Validité d'un numéro de jour pour un mois et une année donnée

Paramètres :

- j : entier - le jour à tester

- mois, annee : entier - les mois et année

Valeur de retour : booléen

- True si j est un jour valide pour le mois mois de l'année annee

- False sinon

Contraintes :

- mois et annee valides

Exemples :

>>> est_jour_valide(-1, 1, 2020)

False

>>> est_jour_valide(32, 1, 2020)

False

>>> est_jour_valide(29, 2, 2018)

False

>>> est_jour_valide(29, 2, 2020)

True

"""

## Validité d'une date

121. Proposez un prédicat pour vérifier la validité d'une date donnée sous la forme de trois valeurs. On vérifiera que

* le mois est valide

* le jour est valide

* la date est postérieure au 15 octobre 1582, que l'on peut considérer comme premier jour du calendrier grégorien.

## Nom du jour d'une date

Commençons par déterminer le nom d'un jour dont on connaît le numéro. Les numéros de noms de jour (à ne pas confondre avec les numéros de jours) sont des entiers entre 0 et 7.

Les valeurs 0 et 7 désignent dimanche, la valeur 1 désigne lundi, 2 mardi, etc. jusque 6 qui désigne samedi.

122. Définissez une fonction nom_jour() :

def nom_jour(njour):

"""

Le nom d'un jour de numéro de jour donné

Paramètre :

- njour : entier - numéro d'un jour, 0 pour dimanche, 1 pour lundi, ...
  6 pour samedi, 7 pour dimanche

Valeur de retour :

- chaîne de caractères - le nom du jour

Contraintes :

- njour compris entre 0 et 7

Exemples :

>>> nom_jour(0)

'dimanche'

>>> nom_jour(1)

'lundi'

>>> nom_jour(6)
'samedi'
>>> nom_jour(7)
'dimanche'
"""

Le numéro de nom de jour d'une date donnée peut être calculé selon différentes méthodes (cf Wikipédia ou Wikibooks). Une méthode est la suivante :

Soit une date donnée sous la forme d'un triplet de nombres (j, m, a).
Le nombre d calculé selon la formule

$$d = (j + a - c + a_1 - ab + k + q) \mod 7$$

est un entier entre 0 et 6 compris, numéro du nom du jour.

Dans cette formule,

* $c = (14 - m) \div 12$ vaut 1 lorsque m vaut 1 ou 2, et 0 lorsque m vaut 3, 4, ..., 12

* ab est la partie séculaire de l'année a, autrement dit le nombre formé des deux premiers chiffres de l'année (par exemple si $a = 2017$, $ab = 20$)

* $a_1$ est le quotient entier de la division de a par 4 (par exemple si $a = 2017$, $a_1 = 504$)

* k est le quotient entier de la division de ab par 4 (par exemple pour $a = 2017$, $k = 5$)

* q est le quotient entier de la division de $31(m + 12c - 2)$ par 12 (par exemple pour $m = 9$, $q = 18$).

123. Définissez une fonction num_jour()

def num_jour(jour, mois, annee):

"""
Numéro du jour dans la semaine d'une date donnée
Paramètre :
- jour : entier - le numéro du jour dans le mois
- mois : entier - le numéro du mois dans l'année
- annee : entier - l'année
Valeur de retour :
- un entier, numéro du jour dans la semaine, de 0 (dimanche) à 6 (samedi)
Contrainte :
- le triplet (jour, mois, annee) doit être une date valide
Exemples :
>>> num_jour(1, 1, 2019)
2
>>> num_jour(1, 1, 2100)
5
"""

124. Utilisez les fonctions précédentes pour vérifier que le 28 septembre 2020 est bien un lundi, vérifier le nom de jour d'aujourd'hui, ou trouver un quel jour de la semaine vous êtes né·e.
# Exercices niveau confirmé

## Sans or ni and

On désire écrire des fonctions pour les opérations booléennes *and*, *or*, et *not*... sans utiliser les opérateurs Python *and*, *or*, et *not*, ni aucun autre opérateur Python.

On utilisera exclusivement les instructions conditionnelles.

125. Définissez une fonction `myown_not()` :

```python
def myown_not(a):
    """
    Équivalent de l'opérateur not
    Paramètre :
        - a : booléen
        Valeur de retour : booléen
        Contrainte : aucune
    Exemples (exhaustifs) :
        >>> myown_not(False)
        True
        >>> myown_not(True)
        False
        """
```

126. Définissez une fonction `myown_or()`. On se souciera particulièrement de respecter la propriété séquentielle de l'opérateur, c'est-à-dire de ne pas évaluer le second opérande quand cela n'est pas nécessaire.

```python
def myown_or(a, b):
    """
    Équivalent de l'opérateur or
    Paramètres :
        - a, b : booléens
        Valeur de retour : booléen
        Contrainte : aucune
    Exemples (exhaustifs) :
        >>> myown_or(False, False)
        False
        >>> myown_or(False, True)
        True
        >>> myown_or(True, False)
        True
        >>> myown_or(True, True)
        True
        """
```

127. Définissez de même une fonction `myown_and()` :

```python
def myown_and(a, b):
    """
    Équivalent de l'opérateur and
    Paramètres :
        - a, b : booléens
        Valeur de retour : booléen
        """
```
Contrainte : aucune
Exemples (exhaustifs) :
>>> myown_and(False, False)
False
>>> myown_and(False, True)
False
>>> myown_and(True, False)
False
>>> myown_and(True, True)
True
"""

128. Si le second opérande des fonctions `myown_or()` ou `myown_and()` n'était pas de type booléen, quelle serait la valeur et le type renvoyés par vos fonctions quand l'exécution séquentielle ne peut être appliquée ?

129. Le comportement décrit à la question précédente est celui de Python.

Donnez quelques expressions Python qui pourraient être utilisées pour mettre cela en évidence.

## Sans else ni elif

Nous désirons (mais pourquoi donc ?) écrire des codes Python équivalents aux constructions conditionnelles `if ... else` et `if ... elif ...else` sans utiliser le `else` ni le `elif`.

### Équivalent du else

En première approximation, le code suivant

```python
if condition_1:
    instructions_1
else:
    instructions_2
instruction_n
```

peut être remplacé par

```python
if condition_1:
    instructions_1
if not condition_1:
    instructions_2
instruction_n
```

130. Montrez en exhibant un contre-exemple que les deux codes précédents ne sont pas équivalents.

131. Proposez deux fonctions `f_else()` et `f_else_contre_exemple()` construites sur la base de votre contre-exemple et correspondant aux schémas suivants.

```python
def f_else(a):
    """
    Paramètre :
        - a : booléen
    Valeur de retour : booléen
        - vrai si et seulement si la partie if a été exécutée
    Exemples :
        >>> f_else(True)
        True
        >>> f_else(False)
        False
    """
    if condition_1 :
        instructions_1
        res = True
    else :
        instructions_2
        res = False
    return res
```
```python
def f_else_contre_exemple(a):
    """
    Paramètre :
        a : booléen
    Valeur de retour : booléen
    (Contre-)Exemples :
        >>> f_else_contre_exemple(True) == f_else(True)
        True
        >>> f_else_contre_exemple(False) == f_else(False)
        True
        """
    if condition_1 :
        instructions_1
        res = True
    if not condition_1 :
        instructions_2
        res = False
    return res
```

Il s'agit de remplacer les `condition_1`, `instructions_1` et `instructions_2` par le code proposé dans votre contre-exemple.

## Équivalent du elif

On considère maintenant un code tel que :

```python
if condition_1:
    instructions_1
elif condition_2:
    instructions_2
elif condition_3:
    instructions_3
else:
    instructions_4
instruction_n
```

132. Proposez un code équivalent qui n'utilise ni `else` ni `elif`.

Menez une démarche identique à celle de l'exercice précédent, « Équivalent du `else` ».
5. Interaction avec l'utilisateur
# Interaction avec l'utilisateur — Programme

## Coup d'œil

On découvre

* ce que sont les interactions avec l'utilisateur et les entrées/sorties

* comment écrire des programmes Python et les exécuter dans un terminal

## Interaction avec l'utilisateur et entrées/sorties

### Interaction avec l'utilisateur

L'interaction avec l'utilisateur permet à des programmes de gérer les échanges avec l'utilisateur, c'est-à-dire avec la personne qui utilise le programme, la personne qui utilise l'ordinateur.

Par exemple :

* un programme peut afficher un texte sur l'écran,

* un programme peut récupérer une entrée saisie au clavier,

* un programme peut dessiner sur l'écran, afficher une image, ou jouer une animation,

* un programme peut jouer un son, voire répondre à des commandes vocales,

* etc.

### Entrées/sorties

Parmi l'ensemble de ces interactions, les entrées/sorties désignent particulièrement les échanges en mode écrit : entrées par le clavier et affichage de texte sur l'écran.

Pour réaliser ces entrées/sorties, des instructions sont proposées dans les langages de programmation.

En Python, on utilise les fonctions prédéfinies `print()` et `input()` que nous découvrirons bientôt.

### Entrées/sorties via un terminal

En tant que *programmeur* Python, nous avons jusque maintenant travaillé dans un environnement de programmation – Thonny en ce qui nous concerne –.

En tant qu'*utilisateur* , nous allons exécuter les programmes Python dans un *terminal* .

Il s'agit d'une fenêtre texte dans laquelle il est possible d'exécuter des commandes du système d'exploitation, de lancer des programmes, dont les programmes que nous avons écrits en tant que *programmeur* Python !

## Programme principal

Un programme est un peu plus qu'un ensemble de définitions de fonctions et d'instructions Python telles que nous les avons écrites jusque maintenant.

Il est en particulier nécessaire d'identifier les instructions à exécuter pour débuter l'exécution du programme.
## Programme principal Python

Un programme, on dit aussi *programme principal* (*main* en anglais), est un ensemble de définitions de fonctions – parfois de variables – contenu dans un fichier. On parle aussi de *script*.

Une bonne pratique est de définir, plutôt en fin de ce fichier, une fonction particulière qui sera le point d'entrée de l'exécution du programme.

Cette fonction sera par exemple nommée `main()`, ou `mon_programme_main()`.

En toute fin de fichier, on précisera par les lignes suivantes qu'un appel à cette fonction doit être réalisé quand débute l'exécution du programme :

```python
if __name__ == '__main__':
    main()
```

Un fichier `.py` correspondant à un programme possédera donc une structure comme :

#

# Mon premier programme Python

#

## définition d'une fonction

```python
def f():
    ...
    return ...
...
```

## définition de main, la fonction principale

```python
def main():
    ...
    ... = f()
    ...
```

## code exécuté à l'exécution du programme

```python
if __name__ == '__main__':
    main()
```

# Annexe — Exécuter un programme dans un terminal depuis Thonny

Exécuter un programme dans un terminal nécessite donc :

* de créer, par exemple dans l'environnement Thonny, un fichier `.py` correspondant au programme,

* de lancer un terminal pour y exécuter ce programme

On utilisera par exemple la commande « *Exécuter le script courant dans un terminal* » du menu « *Exécuter* » de Thonny (ou « *Run current script in terminal* » du menu « *Run* »).

## Memento

* on distingue les rôles de *programmeur* et *utilisateur* d'un programme

* les entrées/sorties permettent à un programme d'interagir avec l'utilisateur

* cette interaction textuelle s'effectue lors de l'exécution d'un programme dans un terminal

* un programme principal comporte une fonction « *main* », point d'entrée de l'exécution du programme
# Écrire sur le terminal

## Coup d'œil

On écrit et exécute notre premier programme !

On découvre

* comment écrire du texte sur le terminal

* que les valeurs de tout type peuvent être écrites sur le terminal

## Écrire sur le terminal

La fonction Python prédéfinie `print()` permet d'écrire sur le terminal.

Elle accepte un nombre variable de paramètres.

Les valeurs de chacun de ces paramètres sont écrites à la suite sur le terminal.

### Hello world !

Notre premier programme va écrire un message de bonjour sur le terminal.

L'instruction Python pour écrire ce message est simplement

```python
print('Hello world!')
```

Le fichier Python qui contiendra notre programme sera donc

```python
# Hello world !, mon premier programme Python
```

```python
# point d'entrée de l'exécution de mon programme
```

```python
def main():
```

```python
    print('Hello world!')
```

```python
# code exécuté à l'exécution du programme
```

```python
if __name__ == '__main__':
```

```python
    main()
```

Voici ce que donne une exécution dans le terminal :

<figure><img src="image_9.png" /></figure>
Ce n'est qu'un premier programme !

*(Le caractère $ affiché à la suite de l'exécution de notre programme est l' invite du terminal, on dit aussi prompt. Il est possible suite à cette invite de saisir d'autres commandes, de lancer l'exécution d'autres programmes.)*

# La fonction print()

## Écriture de tout type

La fonction `print()` écrit donc les valeurs de ses paramètres.

Ces paramètres peuvent être de n'importe quel type. Pour ceux que nous connaissons pour le moment :

* une chaîne de caractères – la suite de caractères est écrite, les caractères les uns à la suite des autres,

* un entier – ses chiffres sont écrits les uns à la suite des autres,

* un flottant – selon sa valeur, il est écrit avec la notation simple ou la notation scientifique (voir le support de cours « *Nombres à virgule flottante* »),

* un booléen – les caractères `True` ou `False` sont écrits.

Par exemple, les instructions suivantes :

```python
from math import pi
```

```python
print('Hello world!')
print(42)
print(pi)
print(pi**42)
print(0 == 0)
```

écriront dans le terminal :

```
Hello world!
42
3.141592653589793
7.590924172052281e+20
True
```

## Fin de ligne et séparateurs

Les valeurs des paramètres de `print()` sont écrites les unes à la suite des autres, séparées par une espace.

Un retour à la ligne est écrit en fin.

Par exemple, les instructions

```python
print("Le résultat du calcul est", 5.1 * 2)
print("(12 + 3) * 5 = ", (12 + 3) * 5)
```

```python
prix = 1.99
poids = 2
print("Les poires sont à", prix, "euros le kilo donc",
       poids, "kg de poires valent", prix*poids, "euros.")
```

produira l'affichage suivant dans le terminal :

```
Le résultat du calcul est 10.2
(12 + 3) * 5 = 75
Les poires sont à 1.99 euros le kilo donc 2 kg de poires valent 3.98 euros.
```

## Fonction et procédure

La fonction `print()` est particulière : elle ne renvoie pas de valeur, elle écrit sur le terminal.

Une telle fonction est appelée *procédure*.

Notre fonction `main()`, point d'entrée de notre programme est elle aussi une procédure.
Memento

* on écrit des valeurs de tout type sur un terminal avec la procédure prédéfinie `print()`

* `print()` écrit sur une ligne les valeurs de ses paramètres séparées par une espace

* une procédure est une fonction qui ne renvoie pas de valeur.
# Lire depuis le terminal

## Coup d'œil

On écrit et exécute notre deuxième programme !

On découvre

* comment lire un texte depuis le terminal

* que des données de tout type peuvent être lues depuis le terminal

## Lire depuis le terminal

La fonction Python prédéfinie `input()` permet de lire des caractères saisis par l'utilisateur via le terminal.

Elle renvoie la chaîne de caractères lue.

## Hello you !

Notre second programme va demander son prénom à l'utilisateur et lui répondre par un message de bienvenue.

L'instruction Python pour lire ce message est simplement

```
prenom = input('Quel est votre prénom ? ')
```

Son exécution affichera le message d'invitation et permettra à l'utilisateur de saisir son prénom que l'on récupérera dans la variable `prénom`.

Le fichier Python qui contiendra notre programme sera par exemple

```
# Hello you !, mon deuxième programme Python
```

```
# point d'entrée de l'exécution de mon programme
```

```
def main():
```

```
prenom = input('Quel est votre prénom ? ')
```

```
print('Bienvenue', prenom, '!!')
```

```
# code exécuté à l'exécution du programme
```

```
if __name__ == '__main__':
```

```
main()
```

Voici les différentes étapes de l'exécution de ce programme dans le terminal :

1. Affichage de l'invitation
<figure><img src="image_10.png" /></figure>

2. L'utilisateur saisi son prénom

<figure><img src="image_11.png" /></figure>

3. Affichage du message de bienvenue

<figure><img src="image_12.png" /></figure>

## La fonction input()

### Lecture de données de tout type

La fonction `input()` ne permet que de lire une chaîne de caractères.

Il est cependant possible de convertir la chaîne de caractères lue en une valeur d'un autre type (voir la section « Conversions » du support de cours « Nombres à virgule flottante »).

Par exemple le résultat d'une exécution dans le terminal des instructions suivantes

```
prenom = input('Quel est ton prénom ? ')
naissance = int(input('Quel ton année de naissance ? '))
print(prenom, 'tu as', 2020-naissance, 'ans ;')
```
pourra être

Quel est ton prénom ? Raymond

Quel ton année de naissance ? 1978

Raymond tu as 42 ans ;)

## Memento

* on lit des chaînes de caractères depuis le terminal avec la fonction prédéfinie `input()`

* les fonctions prédéfinies de conversions telles `int()` ou `float()` permettent de lire des données de tout type
# Exercices niveau débutant

## Premiers print() et input()

### Fonction mystère

Voici le code d'une fonction mystère :

```python
def mystere(n):
    if n == 0 or n == 2 or n == 3:
        print('oooo')
    elif n == 1:
        print(' o')

    if 1 <= n and n <= 3:
        print('  o')
    elif n == 0:
        print('o  o')

    if n > 1 and n < 4:
        print('oooo')
    elif n == 1:
        print('  o')
    elif n == 0:
        print('o  o')

    if n == 2:
        print('o  ')
    elif n == 1 or n == 3:
        print('  o')
    elif n == 0:
        print('o  o')
    else:
        print('...')

    if n == 0 or n == 2 or n == 3:
        print('oooo')
    elif n == 1:
        print('  o')
```

133. Écrivez les lignes affichées dans le terminal par l'exécution de chacune des 5 instructions suivantes :

mystere(0)

mystere(1)

mystere(2)

mystere(3)

mystere(4)

### Saisie de données

134. Écrivez une suite d'instructions permettant de demander à l'utilisateur de saisir deux nombres entiers et affichant la somme de ces nombres, comme dans l'exemple ci-après
Donnez deux nombres entiers ? 12
15
La somme de 12 et 15 est 27

135. Écrivez un programme dont l'exécution dans un terminal permettra à l'utilisateur de saisir deux nombres entiers et affichant la somme de ces nombres.

## Caractères spéciaux

Les caractères spéciaux, ne sont pas écrits tels quels par la fonction `print()`, mais permettent de passer à la ligne ou d'écrire des caractères non accessibles directement.

On les note sous la forme du caractère `\` – antislash ou barre oblique inverse – suivi d'un autre caractère, par exemple `\n`.

Cette barre oblique inverse n'est pas comprise comme un caractère, il ne sera pas écrit comme tel. De même pour le caractère qui suit la barre oblique.

Parmi les caractères spéciaux, notons :

* `\n` qui permet d'insérer un passage à la ligne sur le terminal

* `\t` qui permet d'insérer une tabulation

* `\"` et `\'` qui permettent d'écrire les guillemets

* `\\` qui permet d'écrire le `\` lui-même.

136. Anticipez le résultat de l'exécution des instructions suivantes :

```python
str1 = "Écrivons une première ligne\n et une seconde ligne\n"
str2 = 'Écrivons \n\t - des guillemets simples \' et doubles "\n'
str3 = "\t - des barres obliques inversées \\" ou pas /"
print(str1 + str2 + str3)
```

Utilisez Thonny pour visualiser ce que produit cette exécution dans un terminal.

Ces caractères spéciaux sont écrits avec deux symboles, mais forment bien un unique caractère :

137. Évaluez les expressions suivantes :

```python
>>> len("
")
>>> len("\\")
```

## Jeu du '421'

L'objectif est de réaliser une version très simplifiée du '421' consistant en un seul lancer de 3 dés 6 mais permettant une interaction avec l'utilisateur via le terminal.

Vous pourrez réutiliser des fonctions déjà écrites précédemment.

Commencez par créer un répertoire `mini_jeux` dans lequel vous sauvegarderez un script `quatre_deux_un.py` que vous allez compléter.

À la fin de ce script, définissez une fonction principale `main_421()` qui sera le point d'entrée du programme.

Les fonctions ci-après permettent de décomposer ce programme en fonctions plus simples, indépendantes les unes des autres et qui seront toutes utilisées dans cette fonction principale.

### Fonctions propres au jeu du '421'

138. Définissez une fonction `de()` sans paramètre simulant le lancer d'un dé à 6 faces.

139. Définissez un prédicat `est_un_421` acceptant 3 paramètres correspondant aux valeurs de chacun des dés et testant si ces valeurs forment un triplet 421 (dans n'importe quel ordre).

## Interaction avec l'utilisateur

Il s'agit maintenant d'ajouter quelques fonctions permettant :

* d'afficher des informations à destination de l'utilisateur

* de lui demander de fournir certaines informations.
140. Définissez une fonction `identification()` sans paramètre qui demandera à l'utilisateur de s'identifier à l'aide d'un nom et qui renverra cette valeur sous forme d'une chaîne de caractères

141. Définissez une procédure `presentation()` acceptant en paramètre le nom de l'utilisateur. Elle affichera un message de bienvenue personnalisé et expliquera très brièvement le principe du jeu.

142. Définissez un prédicat `poursuivre_le_jeu()` qui a pour objectif de demander à l'utilisateur s'il souhaite tenter sa chance au jeu en lançant les dés. L'utilisateur répondra par exemple par « oui » ou « non », « yes » ou « no ». Ce prédicat renverra la valeur `True` si et seulement si le joueur désire poursuivre.

## Affichage sur le terminal

La mise en forme des résultats va permettre au jeu d'être plus convivial.

143. Définissez une procédure `affiche_lancer()` acceptant trois paramètres correspondant aux valeurs de chacun des dés.
Cette fonction réalisera un affichage mis en forme des dés comme celui ci-dessous. Respectez strictement les caractères utilisés dans l'exemple :

```
>>> affiche_lancer(4, 3, 5)
```

```
+---+   +---+   +---+
| 4 |   | 3 |   | 5 |
+---+   +---+   +---+
```

144. Définissez enfin une procédure `affiche_resultat()` acceptant les trois mêmes paramètres et affichant un message de victoire ou d'échec suivant la réussite (peu probable en un essai !) du lancer.

## One main to rule them all

Il vous reste à compléter la fonction principale point d'entrée de l'exécution du programme.
Elle appellera les fonctions que vous venez de définir dans un certain ordre.

145. Définissez une procédure `main_421()` sans paramètre.
Cette fonction devra, dans l'ordre :

* procéder à l'identification de l'utilisateur

* présenter le jeu

* vérifier que le joueur souhaite jouer

* si c'est bien le cas :
  - lancer chacun des dés
  - afficher le lancer
  - afficher le résultat du jeu (réussite ou non)

* sinon : afficher un message adapté d'au revoir.

146. On sait comment lancer automatiquement l'ensemble des doctests lors de l'exécution du programme :

```
if __name__ == '__main__':
    import doctest
    doctest.testmodINSTALLATION = True
```

On peut ajouter à ce bloc d'instruction conditionnelle l'appel à la fonction `main_421()`. Que va-t-il alors se passer lors de l'exécution de votre programme dans un terminal ?

147. Lancer l'exécution de votre programme dans un terminal.
# Exercices niveau intermédiaire

## Jeu du bandit manchot

Différents modèles de machine à sous, aussi appelés bandits manchots, existent dans les établissements de jeu. Un modèle simple consiste en trois rouleaux comportant cinq symboles, représentés ici par les caractères `$%#~&`. Les combinaisons gagnantes et les gains correspondants sont indiqués ci-dessous :

<table><thead><tr><th>Combinaison</th><th>Gain</th></tr></thead><tbody><tr><td>trois $ mise × 250</td><td></td></tr><tr><td>trois % mise × 150</td><td></td></tr><tr><td>deux $ mise × 5</td><td></td></tr><tr><td>un seul $ mise × 2</td><td></td></tr><tr><td>tout autre combinaison</td><td>0</td></tr></tbody></table>

On souhaite réaliser un jeu consistant en une seule utilisation de cette machine à sous : le joueur pourra miser une somme de son choix.

Dans le répertoire `mini_jeux` précédent, créez un script `bandit_manchot.py`.

## Fonctions propres au jeu

148. Définissez une fonction `rouleau()` simulant la rotation d'un seul rouleau de la machine et renvoyant un caractère parmi ceux possibles.

149. Définissez une fonction `calcul gain()` acceptant quatre paramètres :

* la mise de départ du joueur

* les 3 symboles correspondant à chaque rouleau

Elle renverra le gain en suivant les indications fournies en amont.

150. Définissez une fonction `chaînedifferentiel()` acceptant deux paramètres : la mise et le gain. Cette fonction renverra une chaîne de caractères présentant la différence entre la mise et le gain précédée d'un signe + ou - selon que le joueur a gagné ou perdu de l'argent.

Par exemple, pour une mise de 100€ et un gain de 500€ :

```python
>>> chaînedifferentiel(100, 500)
'+400'
```

## Interaction avec l'utilisateur

151. En prenant comme modèle le jeu du '421' définissez les fonctions équivalentes suivantes :

* une fonction `identification()`

* une fonction `presentation()`

## Affichage sur le terminal

152. Définissez une fonction `affiche_rouleaux()` acceptant trois paramètres correspondant aux symboles des trois rouleaux après utilisation du `bandit_manchot`.

L'affichage de ces rouleaux devra correspondre exactement à l'exemple ci-après. Il sera précédé d'une
phrase de présentation.

Pour un appel `affiche_rouleaux('%', '$', '&')`, le texte suivant sera écrit sur le terminal :

les symboles tirés au sort sont :
| % | $ | & |
------ ------ ------

153. Définissez une fonction `affiche gain()` acceptant un paramètre : le gain.
Cette fonction réalisera l'affichage de ce gain mis en forme. Les exemples `affiche gain(150)` et `affiche gain(0)` produiront respectivement

Bravo, vous avez gagné 150 € :)

et

Aucun gain, pas de chance :(

154. Définissez enfin une fonction `affiche_bilan()` acceptant deux paramètres : la mise et le gain. Elle procédera à l'affichage du différentiel mis en forme, par exemple pour un appel `affiche_bilan(100, 500)` :

Par rapport à votre mise initiale, le différentiel est de : +400 €

## Fonction principale

155. Définissez une fonction `main_bandit_machot()` sans paramètre.
Inspirez vous de la fonction principale du jeu du 421 mais n'oubliez pas de :

* demander une mise de départ au joueur

* d'afficher le bilan de la partie.

156. Ajoutez les instructions nécessaires pour qu'à l'exécution de ce script les doctests soient vérifiés et que la fonction principale soit aussi automatiquement lancée.

157. Exécutez votre programme dans un terminal.

## Lanceur de mini-jeux

Notre collection de mini-jeux s'étend : il serait judicieux de réaliser un script permettant de lancer les autres scripts déjà codés. C'est ce que nous allons faire.

Toujours dans le répertoire `mini_jeux` créez un nouveau script nommé `mini_jeux.py`.

Voici un premier code proposé pour ce lanceur :

```python
from bandit_manchot import main_bandit_manchot
```

```python
def maingames():
    print('Jouons ensemble\n')
    jeu = input('À quel jeu voulez-vous jouer ? B (B si "bandit_manchot"')
    if (jeu == 'B') :
        main_bandit_manchot()

if __name__ == '__main__':
    maingames()
```

```python
```

158. Expliquez le rôle de la première ligne.
Quelles sont les fonctions disponibles (vous pouvez vous aider du panneau « Variables » de Thonny).

159. Complétez le code ci-dessus pour permettre de jouer également au 421.

160. Exécutez votre programme `mini_jeux.py` dans un terminal.

## Jeu de la roulette

Continuons notre collection de mini-jeux.

La roulette est un jeu de hasard dans lequel chaque joueur mise sur un ou plusieurs numéros, une couleur, la
hauteur ou la parité du numéro qu'il espère être tiré.

Le tirage du numéro s'effectue à l'aide d'une bille jetée dans un récipient circulaire tournant et muni d'encoches ayant des numéros de différentes couleurs (source : Wikipédia).

La roulette possède 37 cases numérotées de 0 à 36 alternativement rouges et noires, à l'exception du zéro qui est vert.

Pour notre jeu simplifié, à chaque tour de jeu, une des mises suivantes est possible :

* un nombre entier quelconque compris entre 1 et 36

* Manque pour les numéros de 1 à 18 (on "manque" la moitié)

* Passe pour les numéros de 19 à 36 (on "passe" la moitié)

* Pair pour un nombre pair

* Impair sur un nombre impair

161. Créez un script `roulette.py` dans le répertoire `mini-jeux`.

## Prédicats

162. Définissez un prédicat `est_manque()` qui pour un numéro donné en paramètre, renvoie `True` si il est `Manque`, `False` sinon (il est alors `Passe`):

```
>>> est_manque(2)
True
>>> est_manque(35)
False
```

163. Définissez un prédicat `estImpair()` qui pour un numéro donné en paramètre, renvoie `True` si il est `Impair`, `False` sinon (il est alors `Pair`):

```
>>> estImpair(2)
False
>>> estImpair(35)
True
```

## Roulez jeunesse

164. Définissez une fonction `lancer()` qui simule un lancer de roulette et renvoie un numéro compris entre 0 et 36.

## Interaction avec l'utilisateur

165. Définissez une fonction `demande_montant()` qui demande au joueur de saisir le montant qu'il veut miser et renvoie un entier qui représente ce montant.
Une interaction dans le terminal pourra être :

```
Quel est le montant de votre mise ? 20
```

166. Réalisez une fonction `demande_mise()` qui demande au joueur la mise qu'il désire faire renvoie la chaîne de caractères saisie par l'utilisateur.
Cette chaîne de caractères peut avoir les valeurs suivantes:

* un numéro compris entre '1' et '36'

* 'Manque'

* 'Passe'

* 'Pair'

* 'Impair'

On considère que l'utilisateur ne saisit pas une autre chaîne. Une interaction dans le terminal pourra être :

```
Quelle mise voulez-vous faire ? Pair
ou
Quelle mise voulez-vous faire ? 1
```
## Calcul des gains

Le gain est calculé en fonction de la mise du joueur et du numéro du lancer.

Les gains ne sont pas cumulables et sont les suivants :

* 36 fois le montant si le numéro misé est celui de la roulette

* 2 fois le montant le type joué (*Pair*, *Impair*, *Manque* ou *Passe*) correspond à celui du numéro de la roulette

* 1,5 fois le montant (arrondi au supérieur) si le numéro joué et celui de la roulette sont tous les deux *Impair* ou tous les deux *Pair*

* 1,5 fois le montant (arrondi à l'inférieur) si le numéro joué et celui de la roulette sont tous les deux *Manque* ou tous les deux *Passe*

* 0 dans tous les autres cas (la mise est perdue)

167. Écrire la fonction `calcul gain()` qui prend en paramètres le numéro du lancer de roulette, la mise choisie par le joueur et le montant misé, et qui renvoie le gain.

*Astuce*: si la chaîne de caractères représentant la case fait moins de 3 caractères, le joueur a joué un nombre, si elle en fait plus, il a joué *Pair*, *Impair*, *Manque* ou *Passe*.

```
>>> calcul gain(6, "Pair", 21)
42
>>> calcul gain(36, "Passe", 21)
42
>>> calcul gain(6, "Impair", 21)
0
>>> calcul gain(5, "5", 20)
720
>>> calcul gain(5, "6", 21)
31
>>> calcul gain(5, "31", 21)
32
```

**Conseil** : il pourra être avantageux de définir deux fonctions annexes par exemple `calcul gain numero()` et `calcul gain pari()` qui permettront de décomposer cette fonction.

## Affichage, bilans, fonction principale...

168. Ajoutez les fonctions suivantes comme pour le script du bandit manchot : `presentation()`, `identification()`, `affiche gain()`, `chaîne différentiel()`, `affiche bilan()`

169. Définissez une fonction `affiche lancer()` qui prend en paramètre le numéro tiré au sort et affiche la valeur de la case ainsi que sa parité et le caractère *Passe* ou *Manque* de cette case. Par exemple, pour les valeurs 23, 32, et 0, s'affichera respectivement :

23 Impair et Passe

32 Pair et Passe

et

0 Tout le monde perd

Vous réutiliserez bien entendu les prédicats précédents.

170. Enfin, définissez la fonction principale `main roulette()` qui s'inspirera très fortement de celle du bandit manchot. N'oubliez pas cependant de :

* saisir la mise

* d'afficher le résultat du lancer

171. Exécutez votre programme dans un terminal.
6. Boucles - for
# Itérable et boucle 'for'

## Coup d'œil

On découvre

* comment répéter l'exécution de certaines instructions sur une série de valeurs

* la notion d'itérable, structure qu'il est possible de parcourir

* comment parcourir une séquence de valeurs telle une chaîne de caractères

Dans certaines situations, on peut vouloir exécuter des mêmes instructions sur une série de valeurs – tels des caractères ou des nombres –. Il s'agit par exemple d'afficher ces valeurs, ou vérifier que des caractères sont des lettres, ou vérifier que des entiers sont pairs, etc.

Il nous faut pouvoir identifier :

* la série de valeurs : ce sera un *itérable*, notion que nous allons découvrir

* les instructions à exécuter : nous utiliserons la boucle `for` de Python

## Itérable

Un *itérable* est une séquence de valeurs que l'on va pouvoir parcourir.

Une ou plusieurs instructions pourront être exécutées successivement sur chacune des valeurs. L'exécution de cette ou ces instructions sur une valeur est appelée une *itération*.

Les *chaînes de caractères* que nous connaissons sont des itérables. Il va être possible de répéter des instructions sur chacun des caractères de la chaîne.

Nous découvrirons d'autres itérables par la suite.

## La boucle d'itération for

La boucle `for` permet la répétition d'un bloc d'instructions sur chacune des valeurs d'un itérable.

On écrira par exemple :

```python
for c in 'aEi42' :
    print('*')
```

qui affichera une ligne avec une `*` pour chaque caractère de la chaîne `'aEi42'`.

Notez les mots clés :

* `for` qui introduit la boucle, et

* `in` qui sépare un identificateur, ici `c`, et l'itérable.

Notez aussi le caractère `:` qui introduit le bloc d'instructions qui sera répété.

On pourra aussi écrire :

```python
for c in 'aEi42' :
    print('*', c)
```
Notez l'utilisation de l'identificateur associé au `for` – ici le c –. Il s'agit d'une variable, dite *variable d'itération*, qui prend successivement la valeur de chacun des éléments de l'itérable – chacun des caractères de notre chaîne `'aEi42'`.

L'exécution de cette boucle `for` affiche

* a

* E

* i

* 4

* 2

dans le terminal.

Cet autre exemple illustre l'utilisation de l'indentation pour identifier le bloc d'instructions du corps de la boucle, et la fin de ce bloc.

*(islower() et isdigit() testent respectivement si le caractère est une minuscule ou un chiffre.)*

L'exécution de

```python
for cc in 'aEi42' :
    print('------------------')
    print(cc, '\t', cc.islower(), '\t', cc.isdigit())
print('==================')
```

écrit

<table><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td>a</td><td>True</td><td>False</td></tr><tr><td>E</td><td>False</td><td>False</td></tr><tr><td>i</td><td>True</td><td>False</td></tr><tr><td>4</td><td>False</td><td>True</td></tr><tr><td>2</td><td>False</td><td>True</td></tr></tbody></table>

sur le terminal.

Ce dernier exemple n'affiche que les lettres minuscules, à raison d'une par ligne :

```python
for cc in 'aEi42' :
    if cc.islower() :
        print(cc)
print('--')
```

produit l'affichage de

a

i

--

Le nombre d'itérations (répétitions) du bloc d'instructions d'une boucle `for` est connu. Il correspond à la taille de l'itérable. Dans le dernier exemple, même si seules les lettres minuscules sont affichées, toutes les lettres de la chaîne `'aEi42'` sont testées, 5 répétitions sont effectuées.
Memento

* un *itérable* est une séquence de valeurs qui va pouvoir être parcourue à l'aide d'une boucle **for**

* les chaînes de caractères sont des itérables

* le bloc d'instructions d'une boucle **for** est répété pour chacune des valeurs de la séquence

* dans ce bloc d'instructions, la *variable d'itération* est associée à la valeur de l'itération courante
# Intervalle 'range'

## Coup d'œil

On découvre

* la notion d'intervalle d'entiers et comment construire de tels intervalles en Python

* comment parcourir des intervalles avec la boucle `for`

## Intervalle

Un *intervalle* est une séquence d'entiers. Par exemple, la suite des entiers entre 2 et 6 forme un intervalle.

Un intervalle est un itérable. Il sera donc possible de parcourir la séquence des entiers avec une boucle `for`.

La construction Python `range()` permet de créer des intervalles. On utilisera `range(a, b)` pour créer l'intervalle des entiers successifs compris entre *a* (inclus) et *b* (exclu).

Par exemple `range(2, 6)` désignera la séquence d'entiers 2, 3, 4, 5.

D'autres utilisations de `range()` sont possibles que nous découvrirons en exercice.

## Itérer sur un intervalle avec for

La boucle `for` peut être utilisée avec une construction `range()` pour exécuter des instructions sur chacune des valeurs d'un intervalle d'entiers.

On écrira par exemple

```python
for i in range(2, 6) :
    print('*')
```

pour afficher quatre lignes avec une `*` dans le terminal.

On écrira par exemple

```python
for i in range(2, 6) :
    print '* ', i
```

pour afficher chacun des entiers compris entre 2 et 6, 6 exclu :

* 2

* 3

* 4

* 5

dans le terminal.

L'exemple suivant illustre à nouveau l'utilisation de l'indentation pour identifier le corps de la boucle.

```
(i%2 == 0 est vrai pour les seuls entiers pairs.)
```

L'exécution de

```python
for i in range (2, 6) :
    print('-------')
```
```python
print(i, i%2 == 0)
print('========')
```

écrit donc

2 True
3 False
4 True
5 False
========

Et enfin, ce dernier exemple n'affiche que les entiers pairs de l'intervalle 2, 6 :

```python
for i in range(2, 6):
    if i%2 == 0:
        print(i)
print('---')
```

soit :

2
4
--

## Memento

* la construction `range()` de Python crée des intervalles d'entiers

* `range(a, b)` est la suite des $b-a$ entiers compris entre $a$ et $b$, exclu

* ces intervalles d'entiers sont des itérables

* une boucle `for` peut donc être utilisée pour parcourir un intervalle d'entiers
# Utilisations classiques de boucles 'for'

## Coup d'œil

On découvre

* les situations typiques d'utilisation de boucles for

## Affichage

Une boucle for peut être utilisée pour afficher une à une les valeurs d'un itérable.

Les instructions de la boucle utiliseront la fonction `print()` pour afficher la valeur courante de la variable d'itération.

Les supports de cours précédents « Itérable et boucle for » et « Intervalle range » ont présenté quelques exemples sur les chaînes de caractères et les intervalles d'entiers.

L'ensemble des valeurs peut être affiché, ainsi que certaines propriétés de ces valeurs. Par exemple, nous avons vu comment :

* afficher le fait qu'un caractère est une lettre minuscule ou un chiffre, ou pas

* afficher le fait qu'un entier est pair ou pas

Il est possible de n'afficher que certaines des valeurs en combinant boucle for et instruction conditionnelle if. Par exemple, nous avons vu comment :

* n'afficher que les lettres minuscules

* n'afficher que les entiers pairs

## Recherche, identification

En parcourant un itérable, il est possible de tester si une valeur donnée est ou non présente dans celui-ci.

Le principe est le suivant :

* une variable booléenne témoin est utilisée pour mémoriser le fait que l'on ait observé ou non cette valeur

* avant de parcourir l'itérable, nous n'avons pas encore observé la valeur recherchée. La variable booléenne témoin est donc initialisée à False

* lors du parcours, si la valeur recherchée est observée, la variable booléenne témoin passe à True

À la suite de la boucle, la variable témoin indique si la valeur recherchée a été observée, ou non.

Le prédicat ci-dessous recherche la présence de la lettre z (en majuscule ou minuscule) dans la chaîne passée en paramètre.

```python
def contient_z(s):
    """
    :param s: (str)
    :contrainte: aucune
    :return: (bool) True si s contient 'z' ou 'Z'
    :exemples:
    >>> contient_z('Test')
    False
```
```python
>>> contient_z("Zoro")
True
>>> contient_z("Est-ce un zèbre ?")
True
>>> contient_z('')
False
""
trouve = False
for c in s:
    if c == 'z' or c == 'Z':
        trouve = True
return trouve
```

Il est également possible de rechercher si un itérable contient au moins une valeur qui répond à un critère donné.

Le principe est le même.

Par exemple, le prédicat `ci-dessous` vérifie si

l'intervalle passé en paramètre contient un multiple de 11.

```python
def contient_mult11(intervalle):
    """
    :param intervalle: (range)
    :contrainte: aucune
    :return: (bool) True si intervalle contient au moins un multiple de 11
    :exemples:
    >>> contient_mult11(range(1,5))
    False
    >>> contient_mult11(range(1,11))
    False
    >>> contient_mult11(range(10,30))
    True
    """
trouve = False
for i in intervalle:
    if i%11 == 0:
        trouve = True
return trouve
```

Remarquons que l'exécution de la boucle `for` se poursuit jusqu'à la fin de l'itérable, même quand la valeur recherchée est trouvée. Nous verrons dans le cours sur les boucles `while` qu'il est possible d'interrompre une boucle dès que la valeur recherchée est trouvée.

## Comptage, accumulation

Le parcours d'un itérable permet de compter le nombre de valeurs de l'itérable, ou – plus intéressant – compter le nombre de valeurs qui correspondent à un critère donné.

Le principe est

* d'initialiser une variable compteur à zéro

* dans la boucle `for`, d'incrémenter ce compteur à chaque fois que l'on observe une valeur qui répond au critère

À la fin de la boucle, le compteur indique le nombre de valeurs trouvées.

Un premier exemple pour compter le nombre de valeurs d'un itérable. (Notez que la fonction prédéfinie `len()` renvoie cette valeur.)

```python
def nvaleurs(iter) :
    """
    :Nombre de valeur d'un itérable
    Paramètre : iter - itérable
    Contraintes : aucune
    Valeur de retour : entier - nombre de valeurs de l'itérable
    Exemples :
    """
```
```python
>>> nvaleurs('aEi42')
5
>>> nvaleurs(range(2, 6))
4
""
compteur = 0
for i in iter :
    compteur = compteur + 1
return compteur
```

La fonction suivante permet de compter le nombre de lettres minuscules d'une chaîne de caractères. On reconnaît l'usage de l'instruction conditionnelle `if` pour identifier les valeurs à comptabiliser.

```python
def nminuscules(s) :
    """Nombre de lettres minuscules dans une chaîne de caractères
    Paramètre : s - chaîne de caractères
    Contraintes : aucune
    Valeur de retour : entier - nombre de lettres minuscules
    Exemples :
    >>> nminuscules('aEi42')
    2
    >>> nminuscules('aeiouy')
    6
    >>> nminuscules('')
    0
    ""
compteur = 0
for c in s :
    if c.islower() :
        compteur = compteur + 1
return compteur
```

Il est aussi possible d'accumuler les valeurs d'un itérable vérifiant un certain critère, par exemple concaténer certains caractères.

Le principe est identique au comptage. Initialement, le résultat est par exemple une chaîne vide. À chaque fois que l'on rencontre un caractère qui correspond au critère, on l'ajoute (c.-à-d. le concatène) au résultat.

La fonction suivante produit une chaîne de caractères formée des seules lettres minuscules d'une chaîne fournie en paramètre.

```python
def minuscules(s) :
    """Chaîne de caractères formée des seules lettres minuscules d'une chaîne donnée
    Paramètre : s - chaîne de caractères
    Contraintes : aucune
    Valeur de retour : chaîne de caractères
    Exemples :
    >>> minuscules('aEi42')
    'ai'
    >>> minuscules('aeiouy')
    'aeiouy'
    >>> minuscules('MAX')
    ''
    ""
les_minuscules = ''
for c in s :
    if c.islower() :
        les_minuscules = les_minuscules + c
return les_minuscules
```
À suivre...

D'autres utilisation classiques des boucles *for* seront présentées plus tard. En particulier quand il nous sera
possible de créer des « listes » ou des « ensembles » de valeurs de tout type.

Memento

* une boucle *for* peut être utilisée pour afficher les valeurs, ou certaines valeurs, d'un itérable

* une boucle *for* peut être utilisée pour vérifier la présence de certaines valeurs dans un itérable

* une boucle *for* peut être utilisée pour compter les valeurs, ou certaines valeurs, d'un itérable
# Boucles 'for' imbriquées

## Coup d'œil

On découvre

* ce qu'est l'imbrication de boucles **for**, et quels en sont les usages

* comment produire les couples et les paires de valeurs prises dans plusieurs itérables

L'imbrication de boucles **for** consiste à écrire une instruction de boucle **for** dans le bloc d'instructions d'une autre boucle **for**. La première boucle est appelée boucle externe et la deuxième la boucle interne ou imbriquée.

L'imbrication de boucles **for** est utilisée pour parcourir plusieurs itérables « simultanément ». En fait, pour chaque itération de la boucle externe, la boucle interne est parcourue entièrement.

## Ensemble des couples

L'imbrication de boucles est un moyen simple de réaliser un « produit cartésien » entre deux (éventuellement plus) séquences de valeurs.

Il s'agit de produire tous les couples de valeurs.

L'exemple suivant affiche les couples de valeurs dont la première composante est une des lettres de la chaîne 'ABC' et la seconde composante un entier de l'intervalle $[1 - 3[$ :

```python
for c in 'ABC':
    for i in range(1, 3):
        print(c, i)
```

l'exécution produit le résultat suivant dans le terminal :

A 1

A 2

B 1

B 2

C 1

C 2

## Ensemble des paires

Pour deux valeurs différentes données, le couple $(a, b)$ est distinct du couple $(b, a)$, l'ordre des valeurs est importante.

Il n'en est pas de même pour les paires : la paire $\{a, b\}$ est égale à la paire $\{b, a\}$, l'ordre des valeurs n'a pas d'importance.

L'imbrication de deux boucles **for** permet de produire l'ensemble des paires d'un itérable.

Par exemple, pour l'intervalle $[1, 2, 3, 4]$, l'ensemble des paires de valeurs distinctes est formé de $\{1, 2\}$; $\{1, 3\}$; $\{1, 4\}$ et $\{2, 3\}$; $\{2, 4\}$, et $\{3, 4\}$.

On peut utiliser le principe suivant pour produire un tel ensemble de paires de valeurs distinctes :

* pour la première valeur de l'itérable, il s'agit de produire les paires en combinant cette première valeur à chacune des valeurs suivantes de l'itérable
* pour la seconde valeur de l'itérable, il s'agit de produire les paires en combinant cette seconde valeur à chacune des valeurs suivantes de l'itérable

* ...

* pour l'avant dernière valeur, il s'agit de produire la paire en combinant cette avant-dernière valeur avec la dernière valeur de l'itérable

* pour la dernière valeur de l'itérable... il n'y a pas de paire à produire.

Pour la *i*-ème valeur, il s'agit donc de produire les paires $\{i, j\}$ avec $j$ appartenant à $]i, max[$.

La borne de début de la boucle imbriquée dépend donc de la valeur de la variable d'itération de la boucle externe.

L'exemple suivant affiche toutes les paires composées d'entiers distincts compris entre 1 et 4 inclus :

```python
for i in range(1, 4):
    for j in range(i+1, 5):
        print(i, j)
```

```
1 2
1 3
1 4
2 3
2 4
3 4
```

## Memento

* on utilise des boucles **for** imbriquées pour produire l'ensemble des couples de deux itérables

* on utilise des boucles **for** imbriquées pour produire l'ensemble des paires d'un itérable
  - les bornes de la boucle interne dépendent alors de la valeur de la variable d'itération de la boucle externe
# Exercices niveau débutant

## Intervalles d'entiers : `range()`

Pour ces premiers exercices, on pourra se passer de rédiger une docstring complète.

172. Écrivez une fonction qui affiche dans la console la séquence des nombres compris entre 10 et 20 inclus.

173. Écrivez une fonction qui affiche dans la console la séquence des nombres compris entre 0 et 20 inclus.

174. Exécutez la suite d'instructions suivante

```python
for i in range(6):
    print(i)
```

Qu'obtenez-vous ? Que représente le paramètre du `range()` lorsqu'il n'y en a qu'un ?

175. Exécutez ces deux nouvelles suites d'instructions

```python
for i in range(10, 21, 2):
    print(i)
```

```python
for i in range(105, 90, -1):
    print(i)
```

Qu'obtenez-vous pour chaque suite ? Que représente chacun des 3 paramètres du `range()` ?

176. Vous venez de découvrir que `range()` accepte de 1 à 3 paramètres. Les deux paramètres manquants sont facultatifs car ils ont des valeurs par défaut.

Quels sont les paramètres facultatifs et quelles sont leurs valeurs par défaut ?

177. Écrivez une expression `range()` avec trois paramètres qui soit équivalente à `range(10)`.

## Recherche

### Minuscules

178. Proposez un prédicat `sont_minuscules()` qui vérifie que tous les caractères d'une chaîne de caractères sont des lettres minuscules ou des espaces. On utilisera la fonction Python prédéfinie `islower()` pour vérifier qu'un caractère est une lettre minuscule :

```python
>>> 'k'.islower()
True
>>> 'K'.islower()
False
>>> ' '.islower()
False
>>> sont_minuscules('des kakis et des kiwis')
True
>>> sont_minuscules('des kakis et des kiwis !')
False
>>> sont_minuscules('des Kakis et des Kiwis')
False
>>> sont_minuscules('')
True
```
# Comptage, accumulation

## Sommes

179. Écrivez une fonction somme_entiers() qui prend en paramètre un entier positif n et qui renvoie la somme des entiers de 1 à n :

```python
>>> somme_entiers(3) == 6
True
>>> somme_entiers(10) == 55
True
```

(On produira cette somme par une boucle for, sans utiliser la « formule du petit Gauss ».)

180. Écrivez une fonction somme_carres() qui prend en paramètre un entier positif n et qui renvoie la somme des carrés des entiers de 1 à n :

```python
>>> somme_carres(3) == 14
True
>>> somme_carres(10) == 385
True
```

181. Écrivez une fonction somme impairs() qui prend en paramètre un entier positif n et qui renvoie la somme des n premiers entiers impairs :

```python
>>> somme impairs(3) == 9
True
>>> somme impairs(10) == 100
True
```

182. Écrivez une fonction somme_alternee() qui prend en paramètre un entier positif n et qui renvoie

$$-1 + \frac{1}{2} - \frac{1}{3} + \dots + \frac{(-1)^n}{n} = \sum_{i=1}^{n} \frac{(-1)^i}{i}$$

On pourra éviter de calculer directement $(-1)^i$ en remarquant que cette valeur « alterne » pour les valeurs de i successives.

```python
>>> epsilon = 10e-10
>>> somme_alternee(10) - somme_alternee(9) <= 1/10 + epsilon
True
>>> 1/10 - epsilon < somme_alternee(10) - somme_alternee(9)
True
```

## Produits

183. Écrivez une fonction factorielle() qui accepte un paramètre entier positif n et qui renvoie la valeur n! (factorielle n) définie comme le produit des entiers de 1 à n :

```python
>>> factorielle(5) == 120
True
>>> factorielle(1) == 1
True
```