Vous êtes sur la page 1sur 43

Info S1 – page 1/ 43

Tp informatique semestre 1

I L’informatique en cpge
1 Python au concours
• Le langage de programmation utilisé en cpge PTSI est Python. Cela signifie que :
* à l’écrit : à l’épreuve Informatique et Modélisation de systèmes physiques, les réponses à de nombreuses questions
consistent à écrire du code Python 1 (au stylo, sur la copie) ;
* à l’oral : un ordinateur avec Python est mis à la disposition du candidat pour l’épreuve TP Sciences Industrielles,
et un exercice de l’épreuve Mathématiques et Algorithmique se déroule sur un ordinateur avec Python.

2 Ressources
• Pour travailler Python tout seul :
* la documentation officielle : docs.python.org/3/
* les cours sur openclassrooms : fr.openclassrooms.com/informatique/python/cours
* le site de l’association France-ioi : france-ioi.org/algo/chapters.php
* le livre open source de Gérard Swinnen : inforef.be/swi/python.htm
* le cours de Robert Cordeau et Laurent Pointal : perso.limsi.fr/pointal/python:courspython3
* et de nombreux autres. . .
• Il existe aussi les PEP (Python Enhancement Proposals), notamment le numéro 8, qui donne des recommandations
(très fortes) sur la manière d’écrire du code Python (peps.python.org/pep-0008/).
• Le memento disponible sur agora est celui de l’épreuve orale Mathématiques et algorithmique. Remarque : à l’épreuve
écrite, il n’y a pas de memento.

3 Limites du programme sur les éléments de langage Python à connaître


• Le programme d’informatique limite assez strictement les éléments de langage Python à connaître (cette liste, qu’on
trouve en annexe du programme d’informatique, est vraiment très courte : elle tient sur une page, pas très dense).
• Mais attention, quatre remarques :
* Il est nécessaire de savoir utiliser de nombreuses fonctions (qui ne sont pas dans cette liste), pourvu qu’une
documentation soit fournie. Il faut les avoir vues et utilisées avant le jour du concours.
* De plus, il y a d’autres capacités exigibles en Python dans d’autres programmes que celui d’informatique 2 .
* Plus important : le but n’est pas d’apprendre à coder en Python, le but est d’apprendre « une bonne discipline
de programmation » 3 .
* Et tout aussi important, le cœur du programme d’informatique de cpge ne concerne pas un langage, mais :
— de l’algorithmique : recherche dans un tableau (séquentielle, dichotomique. . .), tris (insertion, sélection,
partition-fusion, rapide, comptage, bulle. . .), récursivité, algorithmes gloutons, traitement d’image. . .
— de l’analyse des algorithme et des méthodes de programmation : annotation, effet de bord, assertion, correc-
tion d’un algorithme (notion de variant, d’invariant. . .), jeu de tests, complexité d’un algorithme (temporelle,
en espace). . .
1. Et aussi en SQL, vu en deuxième année.
2. Pour ce qui est du programme de Physique Chimie, on y trouve : matplotlib (fonctions de base, hist), numpy (fonctions de base, polyfit),
np.random (fonctions de base, normal), scipy (odeint, bisect). . .
3. Programme d’informatique, page 4
Info S1 – page 2/ 43
— des notions sur la représentation des nombres en mémoire ;
— le modèles des graphes (algorithme de recherche du plus court chemin (Dijkstra)). . .
— des notions sur les bases de données relationnelles : modèle entité-association, bases du langage SQL. . .
— de notions de programmation dynamique (utilisation de dictionnaire) ;
— ...

II Démarrage en douceur : éditeur ou console, affectation, affichage, fonc-


tions, importation de module
1 IDE, console, éditeur
• Pour ces séances de TP, on utilise un environnement de développement intégré (IDE en anglais), qui contient une
console (ou shell, ou ligne de commande, ou interpréteur de commande. . .) et un éditeur. Il existe de nombreux IDE
pour Python. Il en existe aussi en ligne 4 . On utilise EduPython3.0 (dans le dossier Mathématiques) 5 Par défaut, avec
EduPython, la console est dans le cadre du bas, et l’éditeur dans le cadre de droite 6 .
Remarque : il y a en général beaucoup d’autres outils (de débogage notamment) dans un IDE.
• Par défaut, dans ce tp, il faut utiliser l’éditeur : écrire un programme Python, puis l’exécuter (utiliser les raccourcis
claviers : Ctrl+F9 dans EduPython).
• Si on tape une ligne de code dans la console (après le « >>> », qui « invite » l’utilisateur à écrire une commande :
on parle d’invite de commande, ou de prompt en anglais), suivie de Entrée, elle est exécutée immédiatement. Ce n’est
pas ça que nous souhaitons en général. La console nous servira :
* à tester rapidement l’utilisation d’une commande 7 ;
* à visualiser ce que l’exécution d’un programme écrit dans l’éditeur renvoie, soit parce qu’il y a un print() dans
le code, soit parce qu’il y a un message d’erreur.
• Mais on répète : par défaut dans ce tp, le code est à écrire dans l’éditeur, ensuite on l’enregistre, et enfin on l’execute.

2 Premier programme
Q 1. Exécuter le code suivant :
1 import math
2

3 def hypothenuse(a,b):
4 """Renvoyer la longueur de l'hypothénuse d'un triangle
5 Arguments : les longueurs des 2 autres côtés
6 """
7 c = math.sqrt(a**2 + b**2)
8 return c
9

10 L1 = 42 # Voici un commentaire
11 L2 = 5
12 L3 = hypothenuse(L1, L2)
13 print("Longueur de l'hypothénuse : ", L3)
14

15 x = 6.5
16 y = 7.8
17 print('Résultat de ce deuxième calcul de longueur : ', hypothenuse(x, y))
Tout semble normal ? Bien. Cela n’a l’air de rien, mais il y a beaucoup de choses à dire. . .
4. Par exemple Google Colab, pour ne citer que celui-là. . .
5. Il y en a d’autres installés au lycée : IDLEX (dans le dossier Sciences Industrielles), IDLE (dans le dossier Mathématiques), à votre préférence.
6. Par défaut, avec IDLE, la console seule apparaît, et pour obtenir l’éditeur, il faut faire File, New File.
7. Mais au fait, est-ce que np.arange(0, 3, 1) renvoie [0, 1, 2, 3] ou bien [0, 1, 2] ? → Je teste sur la ligne de commande. (C’est la
deuxième réponse qui est la bonne. Vous comprendrez bientôt.)
Info S1 – page 3/ 43
Remarque : tout ce qui suit un caractère # est un commentaire. C’est ignoré par l’interpréteur, et ne sert qu’à rendre
le code plus compréhensible pour celui qui le lit.
Autre remarque : tout le texte entre les triple guillemets n’est pas du code, mais une chaîne de documentation (voir
plus loin).
Autre remarque : le séparateur décimal est le point, et pas la virgule.

3 Fonction
Q 2. hypothenuse, math.sqrt et print sont des objets Python appelés fonctions. Taper type(hypothenuse) dans la
console : l’interpréteur affiche alors le type de l’objet hypothenuse. Faire de même pour print et math.sqrt.
Remarques :
• Ce que renvoie la fonction type ne parle pas de « type. . . » mais de « class. . . ». Pour faire simple, disons que
type et classe sont synonymes.
• Elle renvoie aussi « function_or_method » et pas « function ». Pour faire simple, disons qu’une méthode est
une catégorie particulière de fonction.
• Une fonction Python est très proche de ce qu’est une fonction en mathématiques : elle admet 0, 1, 2 ou plus paramètres
(ou arguments), fait quelque chose, et retourne (ou renvoie) 0, 1, 2 ou plus objets.
Remarque : en fait, une fonction Python ne renvoie jamais 0 objet. Par défaut, elle renvoie toujours l’objet None, qui
est un objet très spécial, qui veut dire « rien », mais qui n’est pas vraiment rien 8 , puisque c’est un objet.
• Une fonction peut être déjà intégrée (built-in) dans le langage Python de base (exemple : print).
• Une fonction peut être déjà intégrée dans un module (ou bibliothèque) qui ne fait pas partie du langage Python de
base, mais qui peut être importée (exemple : math.sqrt). Les importations de module se font toujours au début du
code : ici, la ligne import math est au début du code. À cause de cette ligne, on a accès aux fonctions du module
math, dont math.sqrt (square root : racine carrée).
• Une fonction peut être définie par l’utilisateur (ici, par exemple : hypothenuse). Retenir la syntaxe :
* On commence par l’instruction (statement en anglais) def ;
* suivie du nom de la fonction ;
* suivi, entre parenthèses, des noms donnés aux paramètres (ou arguments) de la fonction (remarques : s’il n’y a
pas de paramètre, il y a quand même une paire de parenthèses avec rien dedans ; les paramètres sont séparés par
des virgules) ;
* suivis d’un « : » (deux-points) ;
* suivi d’un bloc de code constituant le corps de la fonction (= son contenu = ce qu’elle « fait ») : on sait où ce
bloc de code commence et où il se termine parce qu’il est indenté, c’est-à-dire décalé vers la droite par rapport
au reste du code.
On résume la syntaxe :
1 def nom_de_la_fonction(parametre1, parametre2, parametre3):
2 bloc de code qui n est PAS
3 exécuté maintenant mais qui SERA
4 exécuté quand la fonction sera appelée
5 return objet_renvoyé_1, objet_renvoyé_2
• Remarque sur le vocabulaire. On fait parfois une distinction entre paramètres (les noms donnés dans la définition de
la fonction) et arguments (les valeurs qui sont effectivement passées à la fonction lorsque elle est appelée, c’est-à-dire
que son code est exécuté). Mais cette distinction n’est pas faite partout. On ne la fait pas forcément dans ce cours.
• Une fonction peut contenir (ce n’est pas obligatoire, mais c’est très souvent le cas) une instruction return, suivie d’un
espace, puis de l’objet que la fonction renvoie (ou de plusieurs objets, séparés par des virgules). Lorsque l’interpréteur
rencontre l’instruction return, l’exécution de la fonction est interrompue (c’est-à-dire que s’il y a du code écrit après
dans la fonction, il n’est pas exécuté). Lorsqu’on écrit une fonction, il faut qu’il y ait une instruction return dans
son code.
8. Voir youtube.com/watch ?v=hz5xWgjSUlk, vers 55 s.
Info S1 – page 4/ 43
• Pour appeler une fonction (i.e. pour que son contenu soit exécuté), il faut écrire son nom, suivi des arguments qui
lui sont passés (séparés par des virgules, le tout encadré par des parenthèses). Si la fonction renvoie une valeur et
qu’on souhaite la récupérer, il faut procéder à une affectation en plaçant devant le nom de la fonction un objet suivi
d’un signe =. Par exemple, la ligne L3 = hypothenuse(L1, L2) affecte à la variable L3 ce qu’a retourné la fonction
hypothenuse().
• Une bonne pratique de programmation consiste à documenter son code. Une des choses à faire pour cela est d’insérer,
au début du bloc de code définissant le contenu d’une fonction, une chaîne de documentation (docstring) commençant
et finissant par des « """ » (triple guillemets). Elle décrit sommairement ce que fait la fonction, ce qu’elle renvoie,
les arguments qu’il faut lui fournir.
Q 3. Exécuter help(hypothenuse) dans la console : cela affiche la chaîne de documentation. Essayer avec help(print),
avec help(math.sqrt).

4 L’indentation a un sens
• On insiste : en Python, l’indentation a un sens.
• L’indentation, c’est le fait que les lignes de code sont plus ou moins décalées (= indentées) par rapport au bord gauche
de la fenêtre de l’éditeur.
• Des lignes de code possédant la même indentation forment un bloc de code, qui peut correspondre :
* au contenu d’une fonction,
* au code exécuté plusieurs fois quand on utilise une boucle for ou while,
* au code exécuté ou pas quand on utilise une instruction conditionnelle (if).
• Pour indenter, on utilise 4 espaces (mais l’éditeur le fait tout seul en général). Pas plus, pas moins, pas de tabulation
(voir peps.python.org/pep-0008/).
• Il peut y avoir plusieurs niveaux d’indentation (si des boucles for sont imbriquées les unes dans les autres par exemple).

5 Types int et float, typage dynamique


• L1 = 42 se lit plutôt « L1 reçoit 42 » que « L1 égale 42 ». En effet, il s’agit bien d’une affectation (ou assignation
par anglicisme) : on affecte la valeur 42 à l’objet L1.
On insiste : il ne s’agit pas d’une égalité au sens où on écrit une égalité entre deux quantités et on se demande si
cette égalité est vraie ou fausse (ceci se fait avec un double signe égal (==), voir plus loin.
Q 4. Faire afficher le type de L1. Et de x.
• Parmi les types de base en Python on trouve :
* le type int (i.e. integer, i.e. entier) : il sert à représenter en mémoire des nombres entiers ;
* le type float (i.e. flottant, i.e. nombre « à virgule flottante ») : il sert à représenter en mémoire des nombres
réels.
• Parmi les opérations réalisables, on trouve l’addition +, la soustraction -, la multiplication *, la division /, la division
entière //, le modulo % (a % b renvoie le reste de la division entière de a par b), l’élévation à la puissance ** (a**b
renvoie ab ).
• On constate sur l’exemple que Python est un langage à typage dynamique. En effet, l’interpréteur ne détermine le
type de L1 que pendant l’exécution, au moment où il rencontre cet objet pour la première fois. C’est différent d’un
certain nombre d’autres langages, où il faut déclarer les objets (les variables) avant de s’en servir, en leur donnant un
type de manière explicite (en général au début du code). Mais ici, en Python, c’est l’affectation L1 = 42 qui fait que
L1 est de type entier. Et l’affectation x = 6.5 qui fait que x est de type flottant.
• C’est même plus profond que cela. Lors du premier appel de la fonction hypothenuse (à la ligne 12), on lui passe
des entiers en arguments (puisque L1 et L2 sont des entiers). Par conséquent, les paramètres nommés a et b dans
le corps de la fonction sont des entiers. Mais lors du deuxième appel de la fonction hypothenuse, on lui passe des
flottants (x et y). Par conséquent, les paramètres nommés a et b dans le corps de la fonction sont des flottants.
Q 5. Vérifier ceci en faisant afficher (avec un print) le type de a et de b à l’intérieur de la fonction hypothenuse : oui,
cela signifie rajouter les lignes print(type(a)) et print(type(b)) dans le corps de la fonction.
Info S1 – page 5/ 43
6 Retourner (ou renvoyer) et afficher sont deux choses différentes
• La fonction print affiche sur la sortie standard (i.e. dans la console) les objets passés en arguments.
• Remarques :
* s’il y a plusieurs arguments, il sont affichés successivement, et séparés par un espace (c’est le comportement par
défaut, réglable avec le paramètre optionnel sep=' ') ;
* après avoir affiché le ou les objets concernés (ou éventuellement aucun), print affiche un retour à la ligne,
c’est-à-dire que le print suivant affichera quelque chose sur la ligne suivante et pas à la suite sur la même
ligne (c’est le comportement par défaut, réglable avec le paramètre optionnel end='\n', où \n est un caractère
spécial représentant un retour à la ligne).
Q 6. Un exemple rapide pour comprendre les paramètres sep et end :
1 print('Paf', 'Pif', 'Pof')
2 print('Paf', 'Pif', 'Pof', sep='!', end='Fin')
• On insiste maintenant sur la distinction entre renvoyer et afficher. Ce qu’affiche, par exemple, print('Hop !') n’est
pas ce que retourne print('Hop !'). On insiste : print('Hop !') ne retourne pas Hop !
Q 7. Si on veut afficher ce que renvoie print('Hop !'), il faut faire :
1 a = print('Hop !')
2 print(a)
Le faire. Observer la sortie. On détaille ce qui s’est passé :
• D’abord, la ligne a = print('Hop !') est exécutée, donc le corps de la fonction print est exécuté, c’est-à-dire
que « Hop ! » est affiché sur la console. Ensuite, ce que la fonction print renvoie est stocké dans l’objet a.
• Ensuite, la ligne print(a) est exécutée, donc le corps de la fonction print est exécuté, c’est-à-dire que le
contenu de l’objet a est affiché. Et ce contenu, c’est None.
• Conclusion 1 : ce que renvoie print, c’est None.
• Conclusion 2 : si on vous dit qu’une fonction doit renvoyer ceci ou cela, cela ne veut pas dire qu’elle doit l’afficher
avec un print, mais le renvoyer avec un return.
Q 8. Attention, le comportement de l’interpréteur est différent quand on exécute du code dans la console ou un script écrit
dans l’éditeur. On teste cela sur un exemple, en utilisant la fonction pow(x, y) (qui renvoie xy ). Exécuter le code
pow(2, 3) dans la console. Ensuite, écrire un programme ne contenant que la ligne pow(2, 3) et l’exécuter.
Conclusion : quand on exécute une fonction dans la console, ce que renvoie cette fonction est affiché dans la console.
Mais quand une fonction faisant partie d’un programme est exécutée, ce qu’elle renvoie n’est pas affiché. Il faut faire
un print si on veut l’afficher. Et c’est très bien comme ça, car dans l’immense majorité des cas, on ne veut pas
afficher ce que retourne la fonction.

7 Chaîne de caractère
Q 9. Quel est le type de "l'hypothénuse" (demander à Python dans la console avec type("l'hypothénuse")) ? Et
pour 'Résultat' ?
• Le type str (i.e. string, i.e. chaîne) sert à représenter en mémoire des chaînes de caractères.
• Une chaîne de caractères est entourée de délimiteurs, qui peuvent être :
* soit des apostrophes : 'exemple'
* soit des guillemets : "exemple"
Si la chaîne de caractères contient une apostrophe ou un guillemet, mais pas les deux, on peut ruser. Si elle contient
les deux, on peut toujours se débrouiller. Exécuter le programme suivant pour voir comment on fait :
1 a = "Avec l'apostrophe"
2 b = 'Avec "guillemets"'
3 c = 'Avec l\'apostrophe et les "guillemets"'
4 d = "Avec l'apostrophe et les \"guillemets\""
5 print(type(a), type(b))
6 print(a)
Info S1 – page 6/ 43
7 print(b)
8 print(c)
9 print(d)
• \' et \" sont des caractères spéciaux (comme le retour à la ligne \n).
Q 10. Exécuter print('Un\nDeux') dans la console pour voir.

8 Importation de modules
• Il y a différentes façons d’importer un module. On teste ces différentes façons :
Q 11. Première façon de faire, sur un exemple :
1 import math
2 print(math.cos(math.pi))
Le fait d’utiliser import math donne accès à tous les objets définis dans le module math (sur cet exemple : la fonction
cos, le flottant pi, mais aussi d’autres comme sin, tan, exp, log. . .). Pour appeler ces objets, il faut utiliser le
préfixe math.
Q 12. Deuxième façon de faire :
1 from math import cos, pi
2 print(cos(pi))
Le fait d’utiliser from math import cos, pi donne accès aux objets cos et pi du module math (et pas aux autres
objets). Pour appeler ces objets, il est inutile d’utiliser le préfixe math.
Q 13. Variante de la deuxième façon :
1 from math import *
2 print(cos(pi))
Le fait d’utiliser from math import * donne accès à tous les objets définis dans le module math. Pour appeler ces
objets, il est inutile d’utiliser le préfixe math.
Q 14. Troisième façon de faire :
1 import math as m
2 print(m.cos(m.pi))
Le fait d’utiliser import math as m donne accès à tous les objets définis dans le module math. Pour appeler ces
objets, il faut utiliser le préfixe m (qui est un alias de math).
• C’est la troisième façon de faire qu’il faut en général utiliser :
* ce n’est pas le deuxième parce que sans préfixe, on ne peut plus distinguer, par exemple, la fonction cos du module

math de la fonction cos du module numpy (et ces deux fonctions n’ont peut-être pas le même comportement,
d’où des erreurs qui peuvent être très difficiles à déboguer) ;
* et ce n’est pas la première parce qu’il est pratique d’utiliser des alias, qui sont plus courts que le nom complet.

• Voici deux exemples d’importation avec alias beaucoup utilisées en cpge :


import numpy as np
import matplotlib.pyplot as plt
Il est très courant d’utiliser les alias np et plt pour numpy et matplotlib : ne pas en inventer d’autre.

9 Appel de fonction par valeur


• On revient à la ligne de code 17 du programme de la page 2 :
17 print('Résultat de ce deuxième calcul de longueur : ', hypothenuse(x, y))
• Cet appel de la fonction print n’aboutit pas à l’affichage de « hypothenuse(x,y) » sur la console, mais à l’affichage
de ce que renvoie hypothenuse(x,y). On dit que, en Python, les appels de fonction se font par valeur : cela signifie
que les expressions passées en argument sont évaluées avant d’être passées à la fonction.
• Pour cet exemple, l’interpréteur évalue d’abord x et y ; ensuite il passe les valeurs correspondantes en arguments
à hypothenuse. Ensuite, il évalue hypothenuse(x,y) (= il l’exécute). Et enfin, il passe la valeur renvoyée par
hypothenuse en argument à print.
Info S1 – page 7/ 43
10 Portée lexicale
• Dans le programme de la page 2, on a utilisé des noms différents pour les paramètres présents dans la définition de
la fonction hypothenuse (a et b) et pour les objets passés en argument lors des appels de la fonction hypothenuse
(l1 et l2, puis x et y). Ce n’est absolument pas une obligation : on peut utiliser les mêmes noms. Mais il faut avoir
conscience que même s’ils ont les mêmes noms, ce ne sont pas les mêmes objets.
Q 15. Pour s’en convaincre, exécuter le code suivant :
1 def puiss(a, b):
2 print('a =', a, ',', 'b =', b, '("dans" la fonction)')
3 return a**b
4

5 a = 3
6 b = 4
7 print('a =', a, ',', 'b =', b)
8 print(puiss(a, b)) # Premier appel à puiss
9 print()
10 a = 4
11 b = 3
12 print('a =', a, ',', 'b =', b)
13 print(puiss(b, a)) # Deuxième appel à puiss
14 print('a =', a, ',', 'b =', b)
Remarque : la fonction puiss n’est pas très utile en elle-même, elle ne sert qu’à illustrer le propos. L’intérêt de ce
code réside dans le fait d’afficher le contenu des objets a et b juste avant l’appel à la fonction puiss, et ensuite
pendant l’exécution de la fonction puiss.
• Lors du premier appel à puiss : la valeur de a définie ligne 5 est passée en premier argument à puiss, donc le
paramètre a (à l’intérieur de la fonction, i.e. lignes 1 à 3) reçoit la valeur 3. Pour b, c’est la même chose avec la valeur
4 et le deuxième argument.
• Lors du deuxième appel à puiss, la valeur de a définie ligne 10 est passée en deuxième argument à puiss, donc le
paramètre b (à l’intérieur de la fonction, i.e. lignes 1 à 3) reçoit la valeur 4. Et on a le comportement symétrique : la
valeur de b définie ligne 11 est passée en premier argument à puiss, donc le paramètre a (à l’intérieur de la fonction)
reçoit la valeur 3.
Dit autrement : « à l’intérieur » de la fonction, a vaut 4, et « à l’extérieur » de la fonction, a vaut 3.
• Avec le bon vocabulaire maintenant :
* Le a des lignes 5 et 10 est une variable globale. On dit aussi que sa portée est globale : cette variable existe (=
est visible) dans l’ensemble du code (= dans tout le programme).
* Le a des lignes 1 à 3 est une variable locale. On dit aussi que sa portée est locale : elle n’existe que localement,
à l’intérieur du bloc de code constitué par la fonction puiss.
Remarque : ces deux a ne sont pas la même variable.
• On parle de la portée lexicale (ou portée, ou scope en anglais) d’une variable : c’est la portion du programme à
l’intérieur de laquelle cette variable existe.
• Il peut y avoir conflit, par exemple si deux variables a sont visibles en même temps au même endroit du code. C’est
le cas ici à l’intérieur de la fonction puiss. Dans ce cas, l’interpréteur Python donne la priorité à la variable locale.
Mais cela ne veut pas dire que la variable globale est oubliée : on voit bien sur la dernière ligne affichée sur la console
que dès qu’on sort de la fonction puiss, a et b font à nouveau référence aux variables a et b globales.

III Structures de contrôles


1 Boucle for (et listes)
Q 16. Exécuter un premier exemple d’utilisation d’une boucle for :
1 for i in [1, 'a', 2.581e2]:
2 print(i)
Info S1 – page 8/ 43
Le modifier en :
1 l = [1, 'a', 2.581e2]
2 for i in l:
3 print(i)
Attention à cette inépuisable source d’erreurs : l est bien la lettre ℓ, et 1 est bien le chiffre 1.
Remarquer que cela ne change rien à la sortie sur la console.
• Le bloc de code contenu dans la boucle for, i.e. le bloc de code indenté (ici constitué par la seule ligne 3) est exécuté
plusieurs fois. Chaque exécution de ce bloc s’appelle une itération. À chaque itération, la valeur de l’objet i change.
• Noter la syntaxe :
for variablequichange in un_objet_iterable:
un bloc de code
indenté qui est
exécuté plusieurs fois
Q 17. Demander à Python le type de l’objet l (dans la console).
• Le type list (ou liste en français) sert à représenter des listes en mémoire. En Python, une liste est un ensemble
d’éléments, pas forcément de même nature (i.e. pas forcément que des entiers, ou que des flottants. . .), séparés par
des virgules, le tout encadrés par des crochets. Exemple : [1, 2, 3.5] est une liste, mais 1, 2, 3.5 n’est pas une
liste 9 .
• Une liste est mutable (ou muable, ou modifiable), c’est-à-dire qu’on peut la modifier 10 .
• Une liste est un objet itérable, c’est-à-dire qu’on peut itérer dessus, c’est-à-dire qu’on peut l’utiliser dans une boucle
for. Dans cet exemple, à la première exécution du code contenu dans la boucle for (= à la première itération),
l’objet i prend la première valeur de la liste l. À la deuxième itération, i prend la deuxième valeur de l, etc.
• [] est la liste vide.
• On peut faire une liste de listes de listes. . . : [['a', 'b', 'c'], 1, [50, 60, [1, 2, 3, 4, 5]]].
• len(l) renvoie le nombre d’éléments de la liste l (len pour length, longueur en anglais). Appliqué à la liste de la
ligne précédente, cela renvoie 3 (et pas 11).
Q 18. Autre exemple à exécuter :
1 for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
2 print(i)
Remplacer ensuite [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] par range(10) pour obtenir :
1 for i in range(10):
2 print(i)
Puis remplacer range(10) par range(11), puis par range(0, 11), par range(3, 11), par range(-2, 11, 2),
par range(20, 2, -3).
• Retenir que la syntaxe est la suivante : range(debut=0, fin, pas=1), où :
* debut est facultatif, et a une valeur par défaut de 0,
* pas est facultatif, a une valeur par défaut de 1, et peut être négatif,
* fin est exclus 11 .
• range(5) se comporte un peu comme la liste [0, 1, 2, 3, 4], mais n’est pas une liste (faire un type(range(5))
dans la console pour se convaincre). C’est un constructeur qui fournit des éléments au fur et à mesure où on les lui
demande.
9. C’est un tuple, voir plus loin.
10. Oui, c’est logique.
11. Source de nombreuses erreurs. . .
Info S1 – page 9/ 43
2 Instruction conditionnelle if...elif...else... (début)
Q 19. Exemple d’utilisation d’un if... :
1 print('Taper un nombre, puis appuyer sur Entrée')
2 a = float(input())
3 if a==0:
4 print('C\'est nul !')
5 print('C\'est fini...')
Remarques :
• la fonction input() attend que l’utilisateur saisisse quelque chose au clavier ;
• la fonction float() convertit son argument en un flottant (si c’est possible).
• Noter la syntaxe :
if condition:
bloc de code à exécuter
si la condition est vraie
• L’expression a==0 teste si le membre de gauche est égal au membre de droite. Il s’agit bien d’un double signe égal.
Retenir que les opérateurs de comparaisons sont : ==, !=, <, >, <=, >= (respectivement : égal, différent, inférieur strict,
supérieur strict, inférieur ou égal, supérieur ou égal).
Q 20. Cette expression renvoie un objet de type. . . Demander à Python le type de a==0.

3 Type booléen, opérateurs logiques


• Le type bool (boolean, ou booléen en français 12 ) sert à représenter en mémoire des variables booléennes. Un objet
de type booléen n’admet que deux valeur possibles : True et False.
• Le résultat des comparaisons citées ci-dessus est un booléen.
• On peut effectuer des opérations sur des variables booléennes. Retenir que ces opérations sont :
* not a : renvoie le contraire de a (True si a vaut False, et inversement) ;
* a or b : renvoie True si au moins une des deux variables vaut True, renvoie False si les deux valent False ;
* a and b : renvoie True si les deux variables valent True, renvoie False si au moins une vaut False.
• On peut combiner ces opérations, des comparaisons. . . en ajoutant des paires de parenthèses si besoin pour gérer les
priorités (not prioritaire sur and, lui-même prioritaire sur or).
• Remarque : l’évaluation des opérations sur les booléens par Python présente un caractère dit paresseux. On s’intéresse
par exemple à l’évaluation de (a or b) or (c<=2.5) : si (a or b) est True, alors Python n’essaye même pas
d’évaluer (c<=2.5), car le résultat final sera forcément True.

4 Instruction conditionnelle if...elif...else... (suite)


Q 21. Exemple d’utilisation d’un if...else... 13 . Modifier le code précédent pour obtenir :
1 print('Taper un nombre, puis appuyer sur Entrée')
2 a = float(input())
3 if a==0:
4 print('C\'est nul !')
5 else:
6 print('C\'est pas nul.')
7 print('C\'est fini...')
• Retenir la syntaxe :
12. Du nom de George Boole, philosophe et mathématicien britannique.
13. Remarque : if et else signifient si et sinon en français. . .
Info S1 – page 10/ 43
if condition:
bloc de code à exécuter
si condition vaut True
else:
bloc de code à exécuter
si condition vaut False
Q 22. Exemple d’utilisation d’un if...elif...else.... Modifier le code précédent pour obtenir :
1 print('Taper un nombre, puis appuyer sur Entrée')
2 a = float(input())
3 if a==0:
4 print('C\'est nul !')
5 elif a==1:
6 print('Un')
7 elif a==2:
8 print('Deux')
9 else:
10 print('Ni 0, ni 1, ni 2.')
11 print('C\'est fini...')
• Retenir la syntaxe :
if condition1:
bloc1
elif condition2:
bloc2
else:
bloc3

5 Boucle while
Q 23. Une boucle while, comme son nom l’indique, répète l’exécution d’un bloc de code « tant que » une condition est
True. Cette condition est testée avant l’éventuelle exécution du bloc de code. Exemple d’utilisation d’une boucle
while :
1 i = 0
2 print(i)
3 while i<9:
4 i += 1
5 print(i)
• i += 1 aboutit à un résultat similaire à i = i + 1. Retenir que cela s’appelle une affectation augmentée. Il est
préférable d’utiliser une affectation augmentée plutôt qu’un affectation normale 14 . Parmi les affectations augmentées
on trouve +=, -=, *=, /=, **=. . .
• Remarque : ce code produit un effet strictement identique à :
1 for i in range(10):
2 print(i)
En fait, tout ce qu’on fait avec une boucle for peut être fait avec une boucle while, et réciproquement. C’est
simplement plus ou moins pratique selon les cas.
En général, si on connaît à l’avance le nombre d’itérations, c’est a priori une boucle for qui est la plus utile, et sinon
une boucle while.
• Noter la syntaxe :
while condition:
bloc de code à exécuter
tant que condition est True
14. Car c’est (parfois, quand l’opération peut se faire en place) plus économique en mémoire et temps de calcul.
Info S1 – page 11/ 43
6 Instructions break et continue
• L’instruction break interrompt l’exécution d’une boucle (for ou while). On la place en général dans une instruction
conditionnelle (if). On sort alors de la boucle, et l’exécution continue avec le code qui suit cette boucle.
• Lorsque la boucle concernée est contenue dans une fonction, un effet similaire (mais pas forcément identique) à un
break est obtenu avec un return (mais c’est bien l’exécution de la fonction qui est interrompue, et pas seulement
de la boucle).
• L’instruction continue interrompt également l’exécution d’une boucle, mais seulement de l’itération en cours. On
passe directement à l’itération suivante, sans exécuter la fin de l’itération en cours.

IV Retour sur les chaînes de caractères et les listes


1 Chaînes de caractères
• On revient sur les chaînes de caractères.
• Il s’agit du type str (string, chaîne).
• Une chaîne de caractères est encadrée par des délimiteurs (apostrophes comme dans 'chaîne' ou guillemets comme
dans "chaîne").
• Une chaîne est un objet itérable.
Q 24. Essayer :
1 s = 'Touchard Washington'
2 for c in s:
3 print(c)
• Une chaîne est une séquence indiçable, c’est-à-dire qu’on accède à chacun de ses éléments en spécifiant un indice.
• Attention, l’indice du premier élément vaut 0, et pas 1. Et par conséquent, l’indice du dernier élément d’une chaîne
de 10 caractères vaut 9, et pas 10. Ceci est une inépuisable source d’erreurs. . .
• Si s est une chaîne et i un entier, alors s[i] renvoie l’élément numéro i de s. i est appelé indice.
• La fonction len(s) renvoie la longueur (= le nombre d’éléments = le nombre de caractères) de la chaîne s.
Q 25. Essayer cela :
1 s = 'Touchard Washington'
2 l = len(s)
3 print(s[9])
4 print(l)
Puis cela :
1 s = 'Touchard Washington'
2 l = len(s)
3 for i in range(l):
4 print(s[i])
Remarquer que cela fournit le même résultat que le code de la question 24.
• Une chaîne est un objet immutable (ou immuable, ou non modifiable).
Q 26. Essayer, cela renvoie une erreur :
1 s = 'Touchard Washington'
2 s[4] = 'Y'
Mais ceci ne renvoie pas d’erreur :
1 s = 'Touchard Washington'
2 s = 'TW'
Il n’y a pas d’erreur parce qu’en fait, à la deuxième ligne, l’objet s n’est pas modifié : il est supprimé puis recréé.
Info S1 – page 12/ 43
2 Listes
• Il s’agit du type list (ou liste).
• Une liste est une séquence d’objets séparés par des virgules et encadrés par des crochets. Ces objets peuvent être de
types différents.
• [] est la liste vide.
• Comme une chaîne, une liste est un objet itérable (vu précédemment) et indiçable.
• Si l est une liste et i un entier, alors l[i] renvoie l’élément numéro i de l. L’indice du premier élément vaut 0.
• La fonction len(l) renvoie la longueur (= le nombre d’éléments) de la liste l.
• Contrairement à une chaîne, une liste est un objet modifiable (ou mutable, ou muable).
Q 27. Illustration :
1 l = ['a', 1, [3, 60.2], [10, 20, 30]]
2 print(l)
3 n = len(l)
4 print(n)
5 for i in l: # Itérable
6 print(i)
7 print()
8 print(l[2]) # Indiçable
9 print(l[2][1]) # l[2] est elle-même une liste, donc est aussi indiçable
10 l[3] = 'TW' # Modifiable
11 print(l)
12 print(l[n]) # Renvoie une erreur (classique...)
Remarquer le l[i][j] qui permet d’accéder à un élément d’une liste imbriquée dans une liste.

3 Lancé de dés
• Retenir que le module random contient des fonctions fournissant des nombres (pseudo-)aléatoires. En particulier, la
fonction randint(a,b) renvoie un nombre entier tiré aléatoirement entre a et b (ces valeurs étant toutes les deux
inclues), avec une densité de probabilité uniforme.
Q 28. Écrire puis tester une fonction gagne_double() qui :
• ne possède aucun paramètre ;
• réalise un tirage en « lançant deux dés 15 », c’est-à-dire en tirant au sort deux entiers compris entre 1 et 6 ;
• renvoie le booléen True si on a tiré un double (i.e. deux chiffres identiques), ou False sinon.
Indication : pour tester une fonction qui a été écrite dans un script, ne pas l’appeler dans la console. Appelez-là dans
le script.
Autre indication : votre code doit respecter certaines conventions ; vous devez respecter une certain ordre :
• d’abord l’importation des modules ;
• ensuite les définitions des fonctions qui seront appelées dans la suite ;
• et enfin le code proprement dit (qui est ici très court : c’est simplement l’appel de la fonction à tester et le print
qui affiche ce que renvoie cette fonction).
Remarque : pour tester, il peut être utile d’ajouter un print à un endroit précis pour vérifier qu’à cet endroit telle
objet à bien la valeur attendue.
• D’une manière générale, il est indispensable de s’assurer qu’une fonction respecte bien son cahier des charges. Dit
autrement : il faut vérifier que pour chaque entrée possible, la fonction retourne bien la sortie attendue. Tout un jeu
de tests est nécessaire si on veut le faire de manière la plus complète possible 16 . Ces tests peuvent être réalisés « à
la main » (avec un papier et un crayon), ou bien de manière logicielle, en écrivant une ou des fonctions qui vérifient
que la fonction effectue bien le travail attendu.
15. À six faces. . .
16. Même s’il est parfois tout simplement impossible de le faire de manière exhaustive.
Info S1 – page 13/ 43
Remarque : en fait, vous avez déjà l’intuition de ce qu’est un jeu de test. Par exemple, pour tester le code de la
question 21, vous avez eu l’idée de tester une entrée égale à 0, et une autre différente de 0, et de vérifier que le
comportement du programme était bien celui attendu. Il s’agit d’un jeu de test (très sommaire).
Remarque : il peut même arriver que les jeux de tests soit écrits avant le code qu’ils sont censés vérifier. . .
• Ici, vu le caractère aléatoire du processus, il n’est pas envisageable de vérifier de manière exhaustive le comportement
de la fonction gagne_double. Mais cela n’empêche pas de vérifier certaines propriétés. Par exemple, on sait que (si
les dés ne sont pas pipés) la probabilité d’obtenir un double est de 16 . Donc, si on effectue un grand nombre de lancés,
la proportion de double devrait tendre vers 16 .
Q 29. Écrire une fonction test_gagne_double(n) qui :
• prend en paramètre un entier n (le nombre de tirages à effectuer) ;
• effectue n lancés (en appelant n fois la fonction gagne_double(). . .) ;
• renvoie la proportion de lancés gagnés.
Tester. . .

4 Observations météorologiques
Q 30. Télécharger le fichier temp.py depuis l’espace classe vers le dossier où vous rangez vos fichiers Python. Il contient les
listes Tmin et Tmax des températures journalières minimales et maximales relevées à la station météorologique de Le
Mans - Arnage en juillet 2022. Écrire le code permettant de compter le nombre de nuits dites tropicales, c’est-à-dire
pour lesquelles la température nocturne n’est pas descendu sous 20 ◦ C.
Q 31. Faire de même pour le nombre de jours dits de forte chaleur, c’est-à-dire pour lesquels la température a dépassé 30 ◦ C.
Q 32. Les fonctions max(l), min(l) et sum(l) renvoie le maximum, le minimum et la somme des éléments d’une liste l
(de nombres). Écrire une fonction moy(l) qui renvoie la moyenne des valeurs de la liste l (on peut utiliser sum()).
Ne pas utiliser une fonction déjà faite (ne pas utiliser np.mean, par exemple). Tester sur Tmax et Tmin (indication :
on trouve environ 29,6 ◦ C et 16,0 ◦ C).
Q 33. Écrire une fonction maxi(l) qui retourne la valeur du maximum de la liste l, (sans utiliser max, ni sort). Tester sur
Tmax, Tmin, etc.
Remarque sur les tests : Il faut toujours envisager, dans les tests, les éventuels cas particuliers. Ici, les cas particuliers
pourraient être des listes comme [1, 2, 3] ou [3, 2, 1], où le maximum est au début ou à la fin de la liste.
On insiste : il faut que le jeu de tests soit le plus complet possible. En fait, il faut même penser à ce genre de cas
particulier en écrivant la fonction.
Remarque plus générale : évidemment, la fonction maxi ne sert à rien. La fonction max déjà implémentée dans Python
fait plus efficacement le travail. . .
Q 34. Modifier la fonction pour qu’elle retourne la position (= l’indice) et la valeur du maximum de la liste l, dans cet
ordre. Rappel : pour renvoyer le contenu de deux objets a et b, on utilise simplement return a, b.
Q 35. Écrire une fonction max_local(l) qui cherche le premier maximum local de la liste l, et qui renvoie l’indice de ce
maximum et ce maximum (dans cet ordre). Tester sur Tmax, Tmin, [1, 2, 3], [3, 2, 1].
Indication : il y a de multiples façons de faire. . .
Rappel (pas forcément utile vu qu’il y a de multiples façons de faire) : une fois que l’interpréteur Python rencontre
l’instruction return a, l’exécution de la fonction s’arrête (même s’il y a encore du code écrit à la suite dans la
fonction) et le contenu de a est retourné ; par ailleurs, il peut y avoir plusieurs return dans une fonction.

5 Pi
• On peut montrer que le nombre π
4 est égal à la somme (infinie) :
π 1 1 1 1 1
= − + − + + ···
4 1 3 5 7 9

En se limitant à un nombre n + 1 de termes dans la somme, et en multipliant par 4 à gauche et à droite, on obtient
l’expression d’une valeur approchée de π :
Å ã
1 1 1 1 1 n 1
π ≃4× − + − + + · · · + (−1)
1 3 5 7 9 2n + 1
Info S1 – page 14/ 43
Remarque : on peut démontrer que chaque nouveau terme ajouté dans la somme est strictement plus petit (en valeur
absolue) que le précédent, et que l’expression ci-dessus tend vers π lorsque n tend vers l’infini 17 .
Q 36. Écrire une fonction approx_pi(n) qui prend en paramètre un entier n et qui renvoie une valeur approchée de π
calculée comme indiquée ci-dessus, avec n+1 termes dans la somme. Tester.
Q 37. On donne quelques décimales de π : π ≃ 3,141 592 653 589 793 238 462 643. Écrire une fonction decimales_pi(d)
qui prend en paramètre un entier d et renvoie le plus petit nombre de termes n tel que la valeur de π calculée soit
correcte à ±10−d près.
Indication : abs(a) retourne la valeur absolue de a. On peut utiliser la valeur de π donnée par math.pi. On peut
partir de approx_pi(n) et (beaucoup) la modifier.
Commencer à tester avec un d « pas trop grand » (3, 4, 5. . .) car le temps de calcul va rapidement augmenter.
Remarque : pour interrompre le déroulement d’un programme, aller dans la console et faire Ctrl + C.

V Tuples
• Le type tuple (ou n-uplet en français, ou bien tuple prononcé à la française) est très similaire au type list mais il
est immuable (immutable, non modifiable).
• Explication sur cette dénomination : un 2-uplet est aussi appelé un doublet ou un couple, un 3-uplet un triplet, un
4-uplet un quadruplet. . .
• Un tuple est une collection d’éléments (pas forcément de même nature) séparés par des virgules, et éventuellement
encadré par des parenthèses (ce n’est pas obligatoire). Exemples : (1, 2, 3) et (1, 'ab', [5, 6, (10, 20)])
sont des tuples (comportant chacun 3 éléments). On aurait pu noter 1, 2, 3 et 1, 'ab', [5, 6, (10, 20)].
• Les parenthèses ne sont strictement obligatoires que pour le tuple vide : ().
• Dans les autres cas, c’est la présence d’au moins une virgule qui fait que l’interpréteur reconnaît un tuple. Exemple
de tuple à un élément : (1,) ou 1,. Contre-exemple : (1) n’est pas un tuple.
• Un tuple est indiçable (= on fait référence à l’élément d’indice i du tuple t avec t[i]).
• Un tuple est itérable (= on peut utiliser un tuple t dans un for i in t).
Q 38. Demander à Python le type des exemples ci-dessus (dans la console) pour vérifier. . .
Attention, la fonction type() utilisée avec plusieurs arguments ne sert pas à retourner un type mais à définir un
nouveau type. Donc type(1, 2, 3) ne renvoie pas le type de 1, 2, 3 mais une erreur. Pour avoir le type de
1, 2, 3, il faut faire une affectation (a = 1, 2, 3) puis demander le type (print(type(a))).
• À quoi sert un tuple ?
* à la même chose qu’une liste lorsqu’on est certain de ne pas avoir à la modifier (c’est plus économique en
mémoire/temps de calcul) ;
* à faire des affectations multiples (par exemple quand une fonction renvoie plusieurs valeurs, voir ci-dessous) ;
* en particulier, à faire des affectations croisées (pour échanger les valeurs de deux variables, voir ci-dessous) ;
* comme clé dans un dictionnaire 18 , car une liste ne peut pas servir de clé car une clé doit être de type immuable 19 .
Q 39. Exécuter le code suivant pour voir comment fonctionne les affectations multiples, et en particulier croisées :
1 def f():
2 return 7, [10, 20] # return(7, [10, 20]) serait strictement identique
3

4 # Ceci :
5 c, d = f() #Affectation multiple
6 print(c)
7 print(d)
8

9 # Équivaut à :
10 x = f()
Pn
17. La suite de terme général sn = i 4
i=0 (−1) 2i+1 converge strictement vers π. . .
18. Voir plus tard.
19. Voir plus tard, on a dit. . .
Info S1 – page 15/ 43
11 c = x[0]
12 d = x[1]
13 print(c)
14 print(d)
15

16 print()
17

18 a = (1, 2, 3)
19 x, y, z = a
20 print(x)
21 print(y)
22 print(z)
23

24 print()
25

26 # Affectations croisées
27 a = 1
28 b = 2
29 a, b = b, a
30 print(a)
31 print(b)

VI Opérations sur les listes, les chaînes et les tuples


1 Concaténation (+ et *) : listes, chaînes, tuples
• Retenir que le signe +, utilisé avec des listes, des tuples ou des chaînes de caractères, ne réalise pas une addition mais
une concaténation : les deux séquences sont mises « bout à bout ».
Q 40. Exemple :
1 a = 'Mange ' # string
2 b = 'ta '
3 c = 'soupe.'
4 d = a + b + c
5 print(d)
6

7 a = [1, 2, 3] # list
8 b = [10, 11, 12]
9 c = a + b
10 print(c)
11

12 a = (1, 2, 3) # tuple
13 b = (10, 11, 12)
14 c = a + b
15 print(c)
• Retenir que, de même, le signe * ne réalise pas une multiplication mais sert à concaténer plusieurs fois la même
séquence.
Q 41. Exemple :
1 a = 'Hop ! '
2 b = 3*a
3 print(b)
4

5 a = [1, 2, 3]
6 b = 3*a
Info S1 – page 16/ 43
7 print(b)
8

9 a = (1, 2, 3)
10 b = 3*a
11 print(b)

2 Construction par append successifs : listes


• Étant donnée une liste l (qui peut éventuellement être vide), retenir que la fonction l.append(a) ajoute l’élément
a à la fin de la liste l.
Remarque : append() est une méthode associée à la liste l. Explication (simple) : dans la définition de la classe list
(ou du type list si on préfère) sont présentes des définitions de fonctions qui agissent sur les objets de cette classe ;
on appelle ces fonctions des méthodes ; en créant un objet de type list, on crée toutes les méthodes associées ; on
les appelle avec une syntaxe du type nomdelaliste.nomdelamethode.
Q 42. Exécuter le premier de ces programmes, puis modifier pour faire les suivants :
1 x = [] 1 x = [] 1 x = []
2 for i in range(10): 2 for i in range(4, 15): 2 for i in range(2, 12):
3 x.append(i) 3 x.append(i) 3 x.append(i/10)
4 print(x) 4 print(x) 4 print(x)

1 x = [] 1 x = [] 1 x = []
2 for i in range(50, 125, 5): 2 for i in range(0, 75, 5): 2 for i in range(0, 75):
3 x.append(i) 3 x.append(i+50) 3 x.append(i*5 + 50)
4 print(x) 4 print(x) 4 print(x)

Q 43. Écrire le programme permettant de construire la liste [1, 1.001, 1.002, 1.003 ... 2.998, 2.999, 3] (com-
prenant 2001 éléments) à l’aide d’une boucle for ; on appellera cette liste x.
• Remarque : on ne peut pas utiliser la méthode append sur une chaîne ou un tuple, car ce sont des objets immuables.
Le faire renverrait une erreur.
• Autre remarque : on peut remplacer les x.append(a) du code précédent par des x = x + [a] (= opération de
concaténation). Cela aboutit au même résultat mais est beaucoup plus gourmand en mémoire/temps de calcul. En
effet, dans ce cas, l’interpréteur crée une nouvelle liste (ailleurs dans la mémoire), un peu plus grande que x, et y place
le résultat de la concaténation, puis détruit l’ancienne liste. Alors que dans le cas du append, la liste x est modifiée
« en place », simplement en agrandissant un peu l’espace mémoire utilisé par x.
• Remarque sur ces deux remarques : on pourrait donc utiliser cette dernière façon de faire (avec x = x + [a]) avec
des chaînes ou des tuples, même si ce sont des objets non mutables (puisque on recrée un nouvel objet à chaque
opération).

3 Construction « en compréhension » : listes


• Voici une façon plus compacte de construire des listes. On parle de construction en compréhension.
Q 44. Exécuter :
1 x = [i for i in range(50, 125, 5)]
2 print(x)
3

4 x = [i*5 + 50 for i in range(0, 75)]


5 print(x)
Q 45. Même question que la 43, mais « en compréhension ».
• Remarque : en fait, quand on aura besoin de générer de longues listes de nombres, on utilisera plutôt les outils du
module numpy (et le type numpy.ndarray, qui ressemble un peu au type liste, mais pas tout à fait).
Info S1 – page 17/ 43
4 Méthode pop, fonction del : listes
• pop est en quelque sorte la réciproque de append. Retenir que si l est une liste, alors l.pop() renvoie la valeur
du dernier élément de la liste, et supprime ce dernier élément (la liste est raccourcie). l.pop(i) fait de même avec
l’élément d’indice i.
• On ne peut pas utiliser pop sur une chaîne ou un tuple, qui sont immuables.
• del l[i] (delete, supprimer) supprime l’élément d’indice i de la liste l (mais ne renvoie pas cet élément).
• En fait, del est une instruction de base de Python, qui peut s’appliquer à n’importe quel objet (sauf un élément d’un
objet immuable).
Q 46. Essayer tout cela. . .

5 Tranche (en anglais slice) : listes, chaînes, tuples


• Retenir qu’à l’aide du signe :, on peut extraire une sous-liste d’une liste. Cela s’applique également aux chaînes et
aux tuples. On parle de tranche, ou de slice en anglais.
Q 47. Observer la sortie de :
1 a = [10, 11, 12, 4, 7, 8, 3.5, 7, 2]
2 print(a)
3 b = a[3:5]
4 print(b)
5 b = a[3:]
6 print(b)
7 b = a[:5]
8 print(b)
9 b = a[:]
10 print(b)
• Noter la syntaxe : l[indicedebut:indicefin].
• L’élément d’indice indicefin est exclus (même fonctionnement que le range utilisé dans les boucles for).
• Si indicedebut est omis, démarrage au début. De même pour la fin.
• Par conséquent, b = a[:] réalise une copie de a.
• On peut aussi réaliser des tranches (anglicisme : faire du slicing) sur des chaînes ou des tuples.
Q 48. Écrire le code permettant d’extraire la sous-chaîne 'informatique' de la chaîne
s = "L'informatique est ma matière préférée.".

6 Copie superficielle ou non : listes


• On peut maintenant imaginer deux façons (au moins) de faire une copie d’une liste.
Q 49. Essayer :
1 a = [1, 2, 3, 4, 5]
2 x = a
3 y = a[:]
4 print('a =', a, 'x =', x, 'y =', y)
5 a[1] = 88
6 print('a =', a, 'x =', x, 'y =', y)
• Explication : x = a ne réalise pas une « vraie » copie de a, mais fait une copie dite superficielle. En fait, dans ce
cas, x et a sont deux « étiquettes » indiquant la même zone de la mémoire.
Q 50. Pour s’en convaincre, ajouter ce code :
7 print('a is x :', a is x)
8 print('a is y :', a is y)
9 print('adresse a :', id(a))
10 print('adresse x :', id(x))
11 print('adresse y :', id(y))
Info S1 – page 18/ 43
Indications : a is b renvoie un booléen indiquant si a et b pointent vers la même zone de la mémoire. id(a) renvoie
l’adresse mémoire de a (en fait, c’est un identifiant unique, construit à partir de l’adresse de a).
• Pour réaliser une copie (une « vraie », appelée copie profonde), utiliser b = copy(a) (fonction copy()) ou b = a.copy()
(méthode copy()), ou bien b = a[:]. Essayer
• Pour les chaînes et les tuples, la question ne se pose pas, car encore une fois ils sont immuables.

7 in et not in : listes, chaînes, tuples


Q 51. i in l renvoie un booléen : True si l’élément i est dans la liste (ou la chaîne, ou le tuple) l, False sinon. Essayer :
1 l = [1, 2, 3]
2 s = 'abc'
3 t = (1, 2, 3)
4 print(1 in l, 99 in l)
5 print('b' in s, 'k' in s)
6 print(1 in t, 99 in t)

VII Tracé de courbes (module matplotlib.pyplot)


1 Premières figures
Le module intéressant ici est en fait un sous-module du module matplotlib, appelé matplotlib.pyplot.
Q 52. Créer un nouveau fichier python. Importer, au début de ce programme, le module matplotlib.pyplot en lui donnant
l’alias plt.
• La fonction de ce module permettant de créer une courbe (ou plusieurs. . .) est plot. L’import du module ayant été
réalisé conformément à la question précédente, on peut appeler cette fonction par plt.plot(). Celle-ci réalise un
tracé de courbe « point par point » : il faut lui fournir en argument une liste d’abscisses et une liste d’ordonnées
(en fait cela peut être autres choses que des listes : tuple, tableau numpy. . . : on le verra plus loin, mais cela reste
des séquences de nombres). Un tableur (Excel, Atelier Scientifique, Latispro. . .) trace les graphiques d’une manière
similaire : il utilise une colonne de valeurs pour les abscisses et une autre colonne pour les ordonnées.
Q 53. Continuer le programme en définissant deux listes de float et en les passant en argument à plot(), ce qui donne :
1 import matplotlib.pyplot as plt
2 a = [1, 2, 3, 4, 5, 6]
3 b = [0.1, 0.2, 0.7, 0.4, -0.3, -0.8]
4 plt.plot(a, b)
5 plt.show()
Si la ligne plt.show() est omise, le graphique est créé (i.e. il est « quelque part » dans la mémoire de l’ordinateur)
mais pas affiché. Il faut appeler la fonction plt.show() pour que l’affichage soit effectivement réalisé.
Q 54. On peut changer l’aspect de la figure : remplacer plt.plot(a,b) par plt.plot(a,b,"+r") pour des croix rouges ou
par plt.plot(a,b,"og") pour des points verts, ou plt.plot(a,b,"+-g") pour des croix vertes reliées par des traits
verts, ou plt.plot(a,b,"x--g") pour d’autres croix reliées par des pointillés, ou plt.plot(a,b,"+g",a,b,"-r")
pour différencier les couleurs. Remarque : pour effacer une courbe : plt.clf() (clf signifie clear figure : effacer la
figure).
Q 55. On peut améliorer la présentation du graphique. Ajouter, avant la ligne plt.show(), les lignes :
5 plt.xlabel("t (s)")
6 plt.ylabel("U (V)")
7 plt.title("Évolution de la tension en fonction du temps")
Q 56. Essayer également plt.grid() et plt.legend(["U(t)"]). Savoir aussi que plt.savefig("jolie\_figure.pdf"),
plt.savefig("jolie\_figure.jpg"), etc. enregistre l’image de la figure au format choisi (pdf, jpg, etc.). 20
Q 57. On peut tracer plusieurs courbes sur la même figure :
20. La commande print(plt.gcf().canvas.get\_supported\_filetypes()) donne une liste des formats disponibles.
Info S1 – page 19/ 43
1 import matplotlib.pyplot as plt
2 a = [1, 2, 3, 4, 5, 6]
3 b = [0.1, 0.2, 0.7, 0.4, -0.3, -0.8]
4 c = [1.5, 2.5, 8]
5 d = [1.5, 1.8, 1.6]
6 plt.plot(a, b, c, d)
7 plt.show()
On peut aussi changer les couleur et forme : remplacer plt.plot(a, b, c, d) par plt.plot(a, b, '+b', c, d, 'or').

2 Première approche pour tracer une courbe : utilisation des listes


Q 58. Écrire un programme traçant la courbe représentative de la fonction sin, sur l’intervalle de départ [1, 3]. Pour cela,
commencer par récupérer le code de la question 43 pour avoir la liste x des abscisses. Compléter le programme pour
construire aussi la liste des ordonnées, c’est-à-dire la liste des valeurs de sin(1), sin(1,001). . . sin(2,999), sin(3) (on
trouve la fonction sin dans le module math). Faire le tracé, choisir des options de tracé (choix de la couleur, choix de
point, croix, trait. . .) pour obtenir une visualisation correcte, donner des noms aux axes, donner un titre au graphique,
etc.

3 Deuxième (et meilleure) approche : utilisation du module numpy


• Ce module permet d’utiliser un type appelé numpy.ndarray, ou plus simplement array. On dit encore tableau (ou
tableau numpy). « ndarray » signifie n-dimensional array, ou tableau à n dimensions en français. D’un manière simple,
on peut voir un array comme une liste de nombres (pour un array à une dimension), ou une liste de listes de nombres
(pour un array à deux dimensions), etc. Un array a donc un nombre de dimensions défini une fois pour toutes. Il a
aussi une taille (= un nombre d’éléments) définie une fois pour toutes. De plus, tous les éléments le composant sont
du même type (soit des entiers, soit des flottants, soit des complexes 21 ). Tout ceci est différent du comportement
d’une liste.
• Un premier intérêt d’utiliser les array), par rapport à l’utilisation des listes « standard » de Python, est qu’on dispose
de fonctions puissantes pour créer et manipuler des tableaux.
• Un deuxième intérêt (voir plus tard dans l’année) est la création et la manipulation de matrices (i.e. de tableaux à
deux dimensions) et de vecteurs (i.e. de tableaux à une dimension) : on dispose d’un produit matriciel, d’un produit
scalaire. . ..
Q 59. Une possibilité pour obtenir un array (un tableau) est de créer une liste puis de la transformer en array ; utiliser
le code suivant pour faire cela (ce code importe le module numpy avec l’alias np, crée une liste appelée l, puis la
transforme en array appelé t) :
1 import numpy as np
2 l = [2, 4, 6, 8]
3 t =n p.array(l)
4 print(l)
5 print(t)
Remarque : l’alias np est très couramment utilisé. Ne pas en prendre un autre.
Q 60. La fonction zeros permet d’obtenir un tableau rempli de 0 : observer la sortie de print(np.zeros(10)). Puis celle
de print(np.zeros((3,4))) (tableau à deux dimensions, qu’on peut assimiler à une matrice, voir plus tard dans
l’année).
Attention, il y a bien deux paires de parenthèses dans np.zeros((3,4)). En effet, np.zeros prend un seul argument.
Cet argument est un tuple. S’il s’agit d’un tuple de 2 éléments (i.e. un 2-uplet, ou encore un doublet) alors la fonction
renvoie un tableau à deux dimensions. Donc il faut les deux paires de parenthèses. np.zeros((10, 10, 10, 10))
renvoie bien un tableau à quatre dimensions, de forme 10 × 10 × 10 × 10.
Q 61. La fonction ones() permet d’obtenir un tableau rempli de 1. Elle s’utilise de manière similaire à zeros(). Utiliser
ones() pour créer un tableau à une dimension de 10 éléments, rempli de 1. Créer aussi un tableau à deux dimensions
de taille 4 × 5 (i.e. de 4 lignes et 5 colonnes).
21. Oui, il y a un type complex en Python.
Info S1 – page 20/ 43
• La fonction empty permet d’obtenir un tableau vide. Elle s’utilise de manière similaire à zeros et ones. Attention,
vide ne signifie pas rempli de 0 : il peut y avoir n’importe quelles valeurs dans le tableau ainsi défini. Utiliser plutôt
zeros.
Q 62. La fonction linspace permet d’obtenir un tableau rempli de valeurs régulièrement espacées entre une valeur minimale
et une valeur maximale. Commencer par observer ce que renvoie print(np.linspace(0, 10, 31)). Ensuite, créer
et afficher un tableau de 51 éléments, contenant les valeurs 0, 0.01, 0.02. . . 0.48, 0.49, 0.5. Puis un tableau de 2001
éléments contenant les valeurs 1, 1.001, 1.002, 1.003. . . 2.998, 2.999, 3.
Q 63. De manière équivalente à linspace, on peut utiliser arange, qui fonctionne de manière similaire au range utilisé
dans les boucles for, à ceci près que les boucles for fonctionnent avec des entiers exclusivement, alors que arange
(comme « array range ») peut fonctionner avec des flottants. Vérifier que np.arange(0, 1, 0.1) renvoie le même
tableau que np.linspace(0, 0.9, 10).
• Noter les syntaxes :
* np.linspace(début, fin, nombredéléments) (attention, fin est inclus, contrairement au comportement
habituel d’un range par exemple) ;
* np.arange(début=0, fin, pas=1) (fin est exclus, comme pour le range standard, début et pas prennent
0 et 1 comme valeurs par défaut).
Q 64. Les objets array du module numpy étant très similaires aux listes, ils se manipulent parfois de la même façon. Mais
il existe des différences importantes. Comparer en observant la sortie de :
1 import numpy as np
2 a_liste = [1, 2, 3]
3 b_liste = [10, 11, 12]
4 a_array = np.array(a_liste)
5 b_array = np.array(b_liste)
6 print(a_liste + b_liste)
7 print(a_array + b_array)
8 print(3*a_liste)
9 print(3*a_array)
• Retenir que :
* sur des array, + réalise une opération terme à terme (et pas une concaténation, comme avec une liste !) ;
* sur des array, 2*a multiplie chaque élément du tableau a par 2 (et ne renvoie pas un array 2 fois plus long,
comme avec une liste !).
Q 65. Observer le comportement du programme suivant :
1 import numpy as np
2 import math as m
3 xliste = [1, 2, 3]
4 xarray = np.array([1, 2, 3])
5 print(m.sin(xliste))
Il renvoie une erreur : la fonction sin du module math ne peut pas prendre une liste en argument.
Remplacer la dernière ligne par print(m.sin(xarray)). Il y a à nouveau erreur : la fonction sin du module math
ne peut pas prendre un tableau (array) en argument.
Remplacer le dernière ligne par print(np.sin(xarray)). La fonction sin du module numpy peut prendre un array
en argument : elle renvoie alors un array en appliquant la fonction sin sur chaque terme.
• Ce comportement des array, où les opérations sont effectuées « terme à terme » est extrêmement pratique pour
toutes sortes de manipulations numériques. On va beaucoup utiliser cela.
Q 66. Certaines choses se passent de la même façon pour les array que pour les listes :
1 import numpy as np
2 xarray = np.array([1, 2, 3, 8, 9, 10, 4.2])
3 print(xarray)
4 print(xarray[0])
5 print(xarray[2])
6 print(xarray[3:5])
Info S1 – page 21/ 43
Q 67. Refaire un programme permettant de tracer la courbe représentative de la fonction sin, sur l’intervalle de départ [1, 3],
en utilisant les fonctions linspace et sin du module numpy : il n’y a plus besoin de boucle for, contrairement à ce
qui a été fait à la question 58.
• Remarque : c’est comme ça qu’il faut faire, c’est tellement plus simple. . .

4 Quelques compléments sur le tracé de courbe


• Le code qui suit illustre le fait qu’on peut utiliser :
* figure(nom_figure) pour créer une nouvelle figure, et aussi pour y revenir ensuite ;
* subplot(Nlignes, Ncolonnes, k) pour faire un tableau de graphiques Nlignes×Ncolonnes dans une figure,
l’entier k servant à choisir le graphique à tracer (1 pour le premier en haut à gauche, 2 pour le suivant à droite
du premier, et ainsi de suite en parcourant l’ensemble des graphiques ligne par ligne) ;
* suptitle pour donner un titre à toute la figure, titles pour donner un titre au graphique en cours de tracé ;
* xlim et ylim pour choisir les bornes des axes, axis('equal') pour imposer un repère orthonormé ;
* legend pour afficher en légende les label de chaque courbe.
Q 68. Tester (voir le fichier Q68_plot_complement.py sur l’espace classe) :
1 import numpy as np
2 import matplotlib.pyplot as plt
3

4 x = np.linspace(0.01, 5, 100)
5 y1 = np.log(x)
6 y2 = np.sin(x)
7 y3 = np.cos(x)
8 y4 = np.arctan(x)
9 y5 = np.exp(x)
10 y6 = 1 / (1 + x)
11 plt.figure('Évolution des courbes jaunes et vertes')
12 plt.plot(x, y2, '-g', label='La verte')
13 plt.xlabel('Altitude (m)')
14 plt.ylabel('Température (K)')
15 plt.title('Titre')
16 plt.suptitle('suptitle')
17 plt.grid()
18

19 plt.figure('Utiliser subplot')
20 plt.subplot(3, 2, 1)
21 plt.plot(x, y1)
22 plt.subplot(3, 2, 2)
23 plt.plot(x, y2)
24 plt.title('titre 2')
25 plt.xlim(0, 3)
26 plt.ylim(-2, 2)
27 plt.subplot(3, 2, 3)
28 plt.plot(x, y3)
29 plt.subplot(3, 2, 4)
30 plt.plot(x, y4)
31 plt.subplot(3, 2, 5)
32 plt.plot(x, y5)
33 plt.subplot(3, 2, 6)
34 plt.plot(x, y6)
35 plt.title('titre 6')
36 plt.suptitle('suptitle')
37

38 plt.figure('Évolution des courbes jaunes et vertes')


Info S1 – page 22/ 43
39 plt.plot(x, y1, '-y', label='La jaune')
40 plt.axis('equal')
41 plt.legend()
42

43 plt.show()

VIII Modélisation d’une courbe expérimentale


1 Modélisation par une fonction quelconque (avec scipy.optimize.curve_fit)
Q 69. Observer la sortie du programme suivant, qui affiche une courbe constituée de « points expérimentaux » (les valeurs
contenues dans les listes x_exp et y_exp ont été mesurées, ou tout au moins calculées à partir de mesures) :
1 import numpy as np
2 import matplotlib.pyplot as plt
3

4 x_exp = np.array([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4])


5 y_exp = np.array([1.95, 1.38, 1.25, 0.87, 0.74, 0.55, 0.56, 0.52, 0.41])
6 plt.plot(x_exp, y_exp, '+b')
7 plt.legend(['Données expérimentales'])
8 plt.show()
• On Åsouhaite
ã modéliser cette courbe expérimentale à l’aide d’une fonction de la forme f : x 7→ f (x) = A0 ·
x
exp − + A1 . Dit autrement : on cherche les valeurs des constantes A0 , A1 et X0 telles que la courbe re-
X0
présentative de f (x) soit « la plus proche possible » des points expérimentaux (c’est ce que fait l’Atelier Scientifique
avec l’onglet Modélisation). Pour cela, on utilise une fonction de scipy.optimize (le sous-module optimize du
module scipy), appelée curve_fit().
• curve_fit() prend en argument :
* la fonction f ; il faut donc définir cette fonction avant d’utiliser curve_fit ; cette fonction f prend comme
argument un tableau (un array) correspondant à la liste des abscisses, ainsi que toutes les constantes dont il
faut déterminer la valeur (dans notre cas : A0, A1, X0) ; cette fonction f retourne la liste des ordonnées ;
* le tableau (= array) des abscisses (on peut aussi utiliser une liste) ;
* le tableau (= array) des ordonnées (on peut aussi utiliser une liste).
• Explication sur le nom de la fonction : « fit » signifie ajustement ou modélisation 22
Q 70. Ajouter la définition de f dans le programme (juste après les lignes contenant les « import ») :
4 def f(x, A0, A1, X0):
5 return(A0 * np.exp(-x/X0) + A1)
Ajouter le import scipy.optimize (au début, avec les autres import).
Ajouter l’appel à la fonction curve_fit (après la ligne y_exp = \dots) :
popt, pcov = scipy.optimize.curve_fit(f, x_exp, y_exp)
Remarquer l’affectation multiple.
• La fonction curve_fit renvoie deux éléments (un tuple de deux éléments, en fait), chacun de ces éléments étant
lui-même un array.
* Le premier élément (qu’on a ici assigné à popt) contient les valeurs optimales des paramètres. Ici, c’est donc un
array de trois éléments, contenant les valeurs optimales de A0 , A1 et X0 . On insiste : le nombre d’éléments de
cet array dépend du nombre de paramètre de la fonction passée en paramètre à curve_fit.
* Le deuxième (assigné ici à pcov) est aussi un array, à deux dimensions, contenant des informations sur « l’er-
reur » entre la courbe expérimentale et la courbe modèle 23 .
Q 71. On va récupérer les valeurs optimales des paramètres, et ensuite utiliser ces valeurs pour construire deux tableaux
(= array) contenant les abscisses et les ordonnées de la courbe modèle. Pour cela, ajouter juste après la ligne
popt, pcov = sci... :
22. Dans l’Atelier Scientifique (en tp de physique) c’est appelé modélisation (mais ce n’est pas vraiment une appellation correcte).
23. Il s’agit d’une matrice de covariance. . .
Info S1 – page 23/ 43
1 A0_opt = popt[0]
2 A1_opt = popt[1]
3 X0_opt = popt[2]
4 x_opt = np.linspace(0, 4, 1000)
5 y_opt = f(x_opt, A0_opt, A1_opt, X0_opt)
Tracer ensuite la courbe modèle sur la même figure que les points expérimentaux (axes, légende, titre. . .).
• Maintenant vous savez faire cela en tp de physique.

2 Modélisation par une fonction polynomiale (avec numpy.polyfit)


• Si la courbe « modèle » qu’on souhaite utiliser est la courbe représentative d’un polynôme, alors il est plus simple
d’utiliser la fonction polyfit du module numpy.
Explication sur le nom de la fonction : « poly » comme polynôme, et « fit » comme ajustement ou modélisation.
• Pour une modélisation avec un polynôme d’ordre 1, c’est-à-dire avec une droite, la syntaxe est la suivante :
a, b = np.polyfit(x_exp,y_exp,1)
• Ici polyfit renvoie un tuple de deux éléments : a et b, qui sont le coefficient directeur et l’ordonnée à l’origine de la
la courbe « modèle » (du fit pour faire un anglicisme, ou de l’ajustement).
• Pour un polynôme d’ordre n, il faut remplacer 1 par n, et par conséquent polyfit renvoie un tuple de n + 1 éléments
au lieu de 2.
• Remarque : il est inutile de fournir une fonction « modèle » comme avec curve_fit.
• Les données expérimentales auxquelles on souhaite appliquer cette fonction sont les suivantes :
p_exp = [ -0.18, -0.20, -0.24, -0.28, -0.36, -0.40, -0.44, -0.48, -0.52]
pp_exp = [0.41, 0.335, 0.26, 0.225, 0.19, 0.18, 0.175, 0.17, 0.165]
correspondant au relevés expérimentaux de deux grandeurs : p et p′ (elles sont dans le fichier fitDescartes.py à
télécharger sur l’espace classe).
L’expression théorique que ces grandeurs sont censées vérifier est 24 :
1 1 1
− = ′
p′ p f

• Si on trace une courbe en plaçant p′ en ordonnée et p en abscisse (ou l’inverse), la relation théorique nous indique
que la courbe obtenue n’est pas une droite. En revanche, si on place y = p1′ en ordonnée et x = p1 en abscisse, alors
la relation théorique indique qu’on obtient une droite (puisqu’on peut l’écrire y = x + f1′ ).
• On va donc calculer x_exp et y_exp à partir des données expérimentales p_exp et pp_exp, puis tracer y_exp en
fonction de x_exp. Si ces points forment une droite, alors la relation théorique est vérifiée. La fonction polyfit
permet alors de déterminer le coefficient directeur et l’ordonnée à l’origine de la droite « modèle » passant au plus
près des points expérimentaux.
Remarque : en fait, on utilise d’abord polyfit et ensuite seulement on fait le tracé, sur le même graphique, des points
expérimentaux et de la droite modèle. Et alors on peut conclure « à lœil » : a-t-on une droite ou pas ? = la relation
théorique est-elle vérifiée ou pas ?
Q 72. Faire le programme qui permet de conclure si la relation théorique entre p et p′ est vérifiée, et qui détermine la valeur
de f ′ (dans l’affirmative).
• Maintenant vous savez aussi faire cela en tp de physique.

IX Recherche séquentielle dans un tableau unidimensionnel


• Dans une recherche séquentielle, on parcourt tous les éléments de la liste en cherchant celui qui a la propriété
recherchée. Remarque de vocabulaire : séquentiel = à la suite.
• Dans cette partie, on s’intéresse à des algorithme de recherche séquentielle : recherche d’un élément, recherche du
maximum, recherche du deuxième maximum.
24. Relation de conjugaison de Descartes pour une lentille mince.
Info S1 – page 24/ 43
• En s’appuyant sur ces exemples, on commence ensuite à aborder la notion de complexité temporelle.
• On s’intéresse ensuite à un algorithme de comptage des éléments d’une liste, ce qui est l’occasion d’introduire le
concept de dictionnaire (et le type Python correspondant : dict).

1 Trois algorithmes
a Recherche d’un élément dans une liste
• Pour effectuer une recherche séquentielle d’un élément x dans une liste ou une chaîne de caractère L on propose de
procéder de la manière suivante :
* On parcourt les éléments de L un par un ;
* Si l’élément en cours correspond à x on renvoie True ;
* Une fois tous les éléments testés, si aucun ne correspond à x, on renvoie False.
Q 73. Écrire une fonction appartient(x, L) qui renvoie True si x (nombre ou chaîne de caractères d’une seule lettre)
se trouve dans L (liste de nombres ou chaîne de caractères) et False sinon, en faisant une recherche séquentielle.
Remarque : ne pas utiliser in.
Tester sur des listes de nombres, des chaînes.
Q 74. Écrire une fonction liste_position(x, L) qui renvoie la liste (éventuellement vide) de toutes les positions de
l’élément x dans L.

b Recherche du maximum
• On l’a déjà fait à la question 33. Retrouver le code correspondant.

c Recherche du deuxième maximum


• On pose comme contrainte que les fonctions ici définies ne doivent pas modifier la liste passée en argument.
• On suppose aussi que cette liste contient au moins 2 éléments, et qu’ils sont tous différents.
Q 75. Créer une fonction maxi1et2(L) renvoyant dans l’ordre décroissant les 2 premiers maximums de L.
Indication : On peut réutiliser le code de la question 33 et le modifier (beaucoup). L’idée est d’appliquer le code de
la question 33 à la liste, puis de supprimer l’élément maximum qu’on vient de trouver, puis de réappliquer le code de
la question 33. Attention à la contrainte (ne pas modifier L) : vérifier qu’après l’exécution de maxi1et2(maliste),
maliste est identique à ce qu’elle était avant.
Q 76. On veut améliorer l’algorithme, de sorte que la liste L ne soit parcourue qu’une seule fois. Écrire une fonction
maxi1et2_v2(L) qui retourne les deux premiers maximums de L en ne la parcourant qu’une seule fois.

2 Une première approche de la complexité d’un algorithme


• La complexité d’un algorithme quantifie les ressources consommées par son exécution. Elle sert donc à comparer deux
algorithmes. Les deux principales ressources considérées sont la mémoire et le temps d’utilisation du processeur. On
distingue alors deux types de complexités :
* la complexité spatiale, qui quantifie l’espace mémoire utilisé par l’exécution du programme,
* et la complexité temporelle, qui quantifie le temps de calcul.
Dans ce cours, on s’intéresse essentiellement à la complexité temporelle.
• Plus précisément, on s’intéresse à l’expression de la complexité temporelle en fonction de la taille des paramètres
d’entrée de l’algorithme. Si n est la taille de l’entrée (nombre d’éléments dans une liste, par exemple), on cherche à
estimer la complexité comme une fonction f : n 7→ f (n).

a Évaluation de la complexité temporelle : décompte des opérations élémentaires


• Pour simplifier l’évaluation de la complexité temporelle et s’affranchir des problématiques matérielles (vitesse du/des
processeurs, architecture...), on évalue la complexité en nombre d’instructions élémentaires.
Info S1 – page 25/ 43
• On définit une instruction (ou opération, au sens large) élémentaire comme étant une instruction qui a un coût (=
un temps d’exécution) constant, c’est-à-dire indépendant de ses paramètres (en particulier indépendant de la taille de
ces paramètres).
Une opération élémentaire peut être :
* une opération arithmétique (addition, soustraction, multiplication, division, modulo, partie entière. . .) ;
* une comparaison de données (relation d’égalité, d’infériorité. . .) ;
* le transfert d’une donnée (lecture et écriture dans un emplacement mémoire) ;
* une instruction de contrôle (branchement conditionnel, branchement vers une fonction auxiliaire lors de l’appel
de cette fonction. . .).
• Attention : si le branchement vers une fonction auxiliaire est une opération élémentaire, en revanche l’exécution
de la fonction elle-même n’est pas une opération élémentaire : la fonction peut en effet contenir un grand nombre
d’instructions.
De même, la comparaison de chaînes de caractères ou la copie d’une partie d’un tableau (slicing) sont des opérations
très simple à écrire en Python mais loin d’être des opérations élémentaires.
• Toutes les opérations élémentaires sont considérées comme ayant des coûts égaux. C’est justifié si on s’intéresse à
la complexité dans le pire des cas (ce qui est ce qu’on va faire). Dans ce cas, on peut attribuer à une opération
élémentaire quelconque le coût de l’opération élémentaire la plus coûteuse.
• Un petit exemple pour commencer :
1 def conversion1(n):
2 """ Prend en argument n en tour/min.
3 Renvoie la valeur convertie en rad/s.
4 """
5 ns = n / 60
6 w = ns * 2 * 3.14
7 return w
La complexité temporelle de la fonction conversion1 est de 3 opérations (1 / et 2 *).
Q 77. On considère la fonction :
1 def conversion2(n):
2 """ ???
3 """
4 h = n // 3600
5 m = (n - 3600*h) // 60
6 s = n % 60
7 return h,m,s
Que fait la fonction conversion2 ? Quelle est la complexité (temporelle) de cette fonction ?

b Ordre de grandeur
• En général, le nombre d’opérations dépend de la taille de l’entrée. La taille de l’entrée est souvent le nombre de
valeurs d’une liste ou d’un tableau (exemples : cas d’une image, d’une liste de mesures, d’une acquisition, d’un relevé
de compte. . .). En effet, pour traiter tous les éléments de l’entrée, on répète généralement des opérations avec une
boucle.
• Pour comparer des algorithmes, on s’intéresse seulement à l’ordre de grandeur asymptotique de f (n) (n est la taille
de l’entrée) pour n « grand ».
La notation utilisée pour donner cet ordre de grandeur est de dire qu’une fonction f (n) est en O(g(n)) (« en grand
O de g(n) »). Cela ce définit rigoureusement par :

∃n0 ∈ N, ∃c ∈ R+ , tel que ∀n ∈ N, n ≥ n0 =⇒ |f (n)| ≤ c|g(n)|

En informatique, on n’utilisera pas cette définition.


Un peu moins rigoureusement, cette notation indique que dans le pire des cas, la croissance de f (n) ne dépassera pas
celle de la fonction g(n) (g(n) étant en général une fonction usuelle « simple » : 1, n, n2 , ln(n), exp(n) . . . ).
Info S1 – page 26/ 43
• Encore un peu moins rigoureusement, sur un exemple : on suppose que l’analyse d’un algorithme montre que le nombre
d’opérations qu’il effectue s’écrit f (n) = α · n2 , où n est la taille de l’entrée et α une constante réelle positive. Alors
on a f (n) = O(n2 ). Vu la définition du « grand O », cela signifie qu’il existe une constante c telle que, au delà d’une
certaine taille d’entrée n, la complexité f (n) est inférieure à c · n2 .
On dit que la complexité de cet algorithme est en O(n2 ).
• Un autre exemple : on suppose que f (n) = α · n2 + β · n + γ + δ ln(n), où n est la taille de l’entrée et α, δ, γ et
δ sont des constantes réelles. On a également f (n) = O(n2 ), ou dit autrement, la complexité de cet algorithme est
également en O(n2 ). En effet, les termes en n, constant et en ln(n) sont négligeables devant le terme en n2 (car il
existe nécessairement une taille n au-delà de laquelle ces termes sont plus petits que le terme en n2 ) 25 .
• Voici un tableau récapitulant divers « types » de complexité qu’on rencontre en informatique. Ils sont classés « du
plus négligeable au moins négligeable ». Les colonnes de droite montrent des temps de calcul correspondant à ces
complexités, calculé sur la base (arbitraire) de 109 opérations par seconde.
type de complexité t (n = 103 ) t (n = 106 ) t (n = 109 )
O(1) constante 1 ns 1 ns 1 ns
O(ln(n)) ou O(log(n)) logarithmique 7 ns 14 ns 21 ns
O(n) linéaire 1 µs 1 ms 1s
O(n · ln(n)) quasi-linéaire 7 µs 14 ms 21 s
O(n2 ) quadratique 1 ms 17 min 32 ans
O(nk ) polynomiale
O(2n ) exponentielle 10284 ans ...
• Encore un exemple pour voir comment faire en pratique :
def moyenne(L):
moy = 0 # 1 affectation
for k in L:
moy += k # 1 addition * taille de L
moy /= len(L)} # 1 division
return moy
Pour une liste L à n éléments (n = len(L)), la complexité de l’algorithme est f (n) = 2 · n + 2. Donc f (n) = O(n).
On parcourt les n éléments de la liste une fois, la complexité temporelle de l’algorithme est donc linéaire.
• On s’aperçoit qu’en pratique, il est inutile de compter les opérations élémentaires exécutées une seule fois.

c Pire ou meilleur des cas


• Certains algorithmes ont un temps d’exécution qui dépend non seulement de la taille des données mais de ces données
elles-mêmes. Dans ce cas on distingue plusieurs types de complexités :
* la complexité dans le pire des cas : c’est un majorant du temps d’exécution possible pour toutes les entrées
possibles d’une même taille.
* la complexité dans le meilleur des cas : c’est un minorant du temps d’exécution possible pour toutes les entrées
possibles d’une même taille.
* la complexité en moyenne : c’est une évaluation du temps d’exécution moyen portant sur toutes les entrées
possibles d’une même taille supposées équiprobables.
• Exemple de la recherche du premier maximum local : la complexité est en O(1) dans le meilleurs des cas (1er élément
de la liste) et O(n) dans le pire des cas (dernier élément de la liste).
• Dans ce cours, on se contente d’analyser la complexité dans le pire des cas (sauf quelques cas particuliers très simples,
sinon considérer le pire cas si rien n’est précisé).

3 Retour sur les trois algorithme : complexité


a Recherche d’un élément dans une liste
Q 78. On revient à la fonction appartient(x, L) de la question 73. Combien d’éléments parcourt-on dans le meilleur des
cas ? En déduire la complexité dans le meilleur des cas.
25. Ne vous laissez pas abuser par les « un peu moins rigoureusement » : tout cela peut s’écrire et se démontrer tout à fait rigoureusement.
Info S1 – page 27/ 43
Q 79. Combien d’éléments parcourt-on dans le pire des cas ? Conclure sur la complexité linéaire de cet algorithme de
recherche.
Q 80. Quelle est la complexité de la fonction liste_position(x,L) de la question 74 ? Y-a-t-il une différence entre le pire
et le meilleur cas ?

b Recherche du maximum
Q 81. Expliquer pourquoi la complexité de l’algorithme de la fonction maxi de la question 33 est linéaire.

c Recherche du deuxième maximum


• On pose comme contrainte que les fonctions ici définies ne doivent pas modifier la liste L.
• On suppose aussi que L contient au moins 2 éléments, et qu’ils sont tous différents.
Q 82. Expliquez pourquoi la complexité de la fonction maxi1et2(L) de la question 75 est linéaire.
• La fonction maxi1et2_v2(L) de la question 76 ne parcourant la liste qu’une seule fois, sa complexité est linéaire.

X Dictionnaire
1 Le type dict
• En python, un objet de type dict (dictionnary, i.e. dictionnaire) est un ensemble de paires « clé : valeur ». L’appellation
dictionnaire est bien sûr tirée de la ressemblance avec le dictionnaire que tout un chacun connaît, qui est un ensemble
de paires « mot : définition ». La comparaison s’arrête cependant là ; le dictionnaire de la vie courante est trié (dans
l’ordre alphabétique), alors que le dict de python ne l’est pas. De plus, en python, une clé peut être presque n’importe
quel objet (sauf une liste) 26 . La valeur associée à une clé peut être n’importe quel objet.
• {1 : 'a', 'frite' : [1, 2, 3], 'tortue' : (7.5, 'carapace')} est un exemple de dictionnaire en python.
On voit que :
* Un dictionnaire est encadré avec des accolades.
* Entre les accolades, on trouve des paires clé :valeur, séparées par des virgules.
* Une clé et la valeur associée sont séparées par « : ».
* Cet objet ressemble à une liste de listes de deux éléments (par exemple, l’exemple ci-dessus ressemble à
[[1, 'a'], ['frite',[1, 2, 3]], ['tortue', (7.5, 'carapace')]). Mais attention, il n’est pas une
liste de listes de deux éléments. En particulier, il n’est pas indiçable.
Q 83. Créer et visualiser le dictionnaire d avec le code :
1 d = {1 : 'a', 'frite' : [1, 2, 3], 'tortue' : (7.5, 'carapace')}
2 print(d)
Q 84. Ajouter ensuite le code suivant pour voir comment accéder à un élément du dictionnaire, modifier un élément, ajouter
un élément, supprimer un élément, itérer sur les clés, itérer sur les valeurs, itérer sur les paires clé :valeur, tester
l’existence d’une clé / d’une valeur / d’une paire clé : valeur, constater que l’ordre des éléments n’a pas d’importance,
créer un dictionnaire vide :
3 print(d['frite']) # d['frite'] renvoie la valeur associée à la clé 'frite'
4 # dans le dictionnaire d.
5 d['frite'] = 'pomme de terre' # Modifie la valeur associée à la clé 'frite'
6 print(d) # dans le dictionnaire d.
7 d['physique'] = 999 # Crée dans le dictionnaire d une nouvelle entrée avec
8 print(d) # la clé 'physique' et la valeur 999
9 for i in d.keys(): # Itération sur les clés
10 print(i)
11 for i in d: # Également itération sur les clés (uniquement)
12 print(i)
26. En fait, une clé doit être un objet non muable, de manière à pouvoir être hachable (voir un peu plus loin). En particulier, une clé ne peut
pas être une liste ou un dictionnaire.
Info S1 – page 28/ 43
13 for i in d.values(): # Itération sur les valeurs
14 print(i)
15 for i in d.items(): # Itération sur les paires clé : valeur
16 print(i)
17 for i, j in d.items(): # Même chose avec une affectation conjointe
18 print('À la clé', i, 'correspond la valeur', j)
19 print(len(d)) # len(d) renvoie le nombre d'entrées de d
20 del(d['frite']) # Supprime la paire clé:valeur dont la clé est 'frite'
21 print(d)
22 print(1 in d, 2 in d) # Teste si 1, puis 2, est une clé de d
23 print(1 in d.keys(), 2 in d.keys()) # Même chose
24 print('a' in d.values(), 'b' in d.values()) # Teste si 'a', puis 'b' est une valeur de d
25 print((1, 'a') in d.items(), (1, 'b') in d.items()) # Teste si une paire clé:valeur est dans d
26 # (remarquer l'utilisation d'un tuple)
27 d1 = {1:'un', 2:'deux'}
28 d2 = {2:'deux', 1:'un'}
29 print(d1 == d2) # L'ordre n'a pas d'importance
30 d3 = {} # Crée un dictionnaire vide

2 Recherche dans un dictionnaire


• Un intérêt de l’utilisation d’un dictionnaire plutôt que d’une liste de listes de deux éléments est que le test d’apparte-
nance d’un élément du dictionnaire se fait plus rapidement qu’en utilisant une liste. En effet, en utilisant une liste, il
faut, dans le pire des cas, parcourir l’ensemble des n éléments de la liste pour trouver (ou pas) l’élément recherché :
on a une complexité linéaire (c’est-à-dire en O(n)). En revanche, les dictionnaires utilisent une technique basée sur
des fonctions de hachage qui permet de trouver (ou pas) un élément du dictionnaire avec une complexité constante
(c’est-à-dire en O(1)).
• Les fonctions de hachage seront vues (un peu) en deuxième année. En simplifié (si vous ne saisissez pas tout, ce n’est
pas grave, ne perdez pas trop de temps là-dessus) :
* Une fonction de hachage associe un entier à n’importe quelle clé. Cet entier est unique (en fait pas tout à fait,
mais on arrive à gérer ce problème). En revanche, pour deux clés identiques, la fonction de hachage renvoie
toujours le même entier.
* Quand on ajoute un élément (= une paire clé :valeur) dans un dictionnaire, python calcule la fonction de hachage
de la clé. Un tableau de correspondance stocke les résultats de ce hachage, et la position en mémoire de la paire
clé :valeur qui lui correspond.
* Quand on souhaite accéder à un élément (= une paire clé :valeur) du dictionnaire qui correspond à une clé
donnée, python calcule la valeur de hachage (on dit aussi le hash, en anglais) de cette clé, et peut ainsi, via le
tableau de correspondance, accéder directement à l’élément du dictionnaire voulu.
* En fait, il peut arriver que deux clés différentes renvoie la même valeur de hachage. On parle de collision. On
gère ces collisions en stockant les paires clé :valeur associées à la même valeur de hachage au même endroit, et
en parcourant la liste de ces paires clé :valeur lorsqu’on souhaite accéder à l’une d’entre elles (ce qui ne génère
pas trop de complexité temporelle car on se débrouille pour que le nombre de clés possédant la même valeur de
hachage reste faible).
• On cherche à vérifier « expérimentalement » que l’accès à un élément d’un dictionnaire est plus rapide que l’accès à un
élément d’une liste de liste de deux éléments. Pour cela, on mesure le temps mis pour exécuter le code correspondant,
en utilisant la fonction perf_counter_ns() du module time. Cette fonction renvoie un entier représentant le temps
écoulé, en nanosecondes, depuis une origine arbitraire. Utiliser une syntaxe ressemblant à :
import time
tdebut = time.perf_counter_ns()
le code
à
exécuter
duree = time.perf_counter_ns() - tdebut
Info S1 – page 29/ 43
Q 85. Le fichier ville_population_dict.py disponible sur l’espace classe contient la définition d’un dictionnaire appelé
ville_pop_dict. Ce dictionnaire comprend plus de 4000 paires clé :valeur, où clé est le nom d’une ville (stocké dans
une chaîne de caractère) et valeur est le nombre d’habitants de cette ville (stocké dans un entier). Écrire le code qui
permet de récupérer le nombre d’habitants de la ville de Zenica 27 et de le stocker dans une variable nommée pop :
cela prend une ligne. Compléter le code pour mesurer le temps que prend l’exécution de cette ligne de code. Ajouter
ensuite un print pour afficher ce temps d’exécution (et aussi le nombre d’habitants de Zenica, bien sûr 28 .)
Remarque : ne pas inclure le print dans la portion de code dont on mesure le temps d’exécution. En effet, afficher
quelque chose prend énormément de temps, cela fausse complètement la mesure.
Q 86. Le fichier ville_population_list.py contient la définition de la liste ville_pop_list. Cette dernière contient
exactement les mêmes données que le dictionnaire ville_pop_dict, mais sous la forme d’une liste (de plus de 4000
éléments) de listes de deux éléments (le premier est le nom de la ville, le deuxième est le nombre d’habitants). Refaire
exactement le même travail que précédemment. Remarque : cette fois, la recherche du nombre d’habitants de Zenica
et l’affectation de ce nombre dans la variable pop prend plus qu’un ligne. En effet, il faut faire une boucle for, tester
l’égalité avec le nom de la ville, etc.
Q 87. Comparer les temps d’exécution. Remarque : Refaire le test plusieurs fois, car le temps n’est jamais tout à fait le
même. En effet, il dépend des autres processus qui sont en train de tourner en même temps.
On constate que la version « dictionnaire » est bien plus efficace que la version « liste ».
Refaire aussi le test en cherchant une ville vers le début de la liste (disons Accra pour fixer les idées) ou vers le milieu
de la liste (disons Lafayette), avec la version « dictionnaire » et avec la version « liste ». Suivant la manière dont
vous avez codé la recherche dans une liste, la performance de cette version s’améliore (ou pas) quand on cherche une
ville vers le début de la liste. Expliquer.
Q 88. Question bonus (si vous avancez vite : demandez au professeur). Pour vraiment constater expérimentalement que
l’accès à un élément d’un dictionnaire se fait en temps constant, il faut mesurer le temps d’exécution de l’accès à un
dictionnaire de n éléments, pour différentes valeur de n. On peut ensuite tracer la durée d’exécution en fonction de
n, et constater qu’on a (à peu près) une constante. Le faire. Indication : dict(L) renvoie une dictionnaire construit
à partir d’une liste de listes de deux éléments L.

3 Comptage des éléments d’un tableau à l’aide d’un dictionnaire


• Remarque préliminaire : ici, lorsque vous avez besoin de balayer les caractères d’une chaîne, il faut faire une boucle.
Autre remarque : évidemment, il existe de nombreuses fonctions (par exemple la fonction counter du module
collections) qui font a priori mieux le travail que ce qui va être codé ici. Mais ce n’est pas la question. . .
Q 89. Question bonus (si vous avancez vite : demandez au professeur). Écrire une fonction estADN(c) qui prend en paramètre
une chaîne de caractère c et qui renvoie True si cette chaîne ne contient que les caractères A, C, G et T, ou bien si
la chaîne est vide. Sinon, elle renvoie False. Remarque : L’ADN est un enchaînement de nombreuses bases azotées
de quatre types : adénine, cytosine, guanine et thymine. On peut les représenter par les lettre A, C, G et T.
Tester sur la chaîne de caractère présente dans le fichier adn.py, ainsi que sur des cas particuliers pertinents. Quelle
est la complexité de cette fonction ?
Q 90. Écrire une fonction compte_caracteres(s) qui prend une chaîne s en argument et qui renvoie un dictionnaire où les
clés sont les caractères présents dans la chaîne (et seulement ceux-là) et les valeurs associées le nombre d’occurrences
du caractère correspondant.
Tester sur la chaîne de caractères contenue dans le fichier adn.py, qui contient un petit extrait du code génétique du
chromosome humain numéro 1.
Tester aussi sur la liste de mots contenue dans le fichiers mots.py. Attention, il faut d’abord convertir cette liste de
chaînes en une seule grande chaîne pour pouvoir la passer en argument à compte_caracteres().
Q 91. Écrire une version modifiée de la fonction précédente, qu’on appelle compte_caracteres_v2(s), qui compte pour
un e les caractères e, é, è, ê et ë, pour un u les caractères ù et û, etc.
Tracer ensuite le nombre de d’occurrences en fonction du caractère (plt.plot fonctionne avec des listes de nombres,
mais aussi avec des listes d’autre chose : commencez par essayer plt.plot(['a', 'b', 'c'], [1, 5, 2]) pour
vous convaincre).
27. C’est en Bosnie.
28. Spoiler : 96 027 habitants
Info S1 – page 30/ 43
XI Algorithmes avec boucles imbriquées
1 Algorithme inconnu
Q 92. On donne le code de la fonction fonction(L), qui prend en argument une liste :
1 def fonction(L):
2 for i in range(len(L)):
3 for Lj in L[:i] + L[i+1:]:
4 if L[i] == Lj:
5 return True, Lj
6 return False
• Quelles valeurs prend i ?
• Quelles valeurs prend Lj pour un i donné ?
• Quand la fonction renvoie-t-elle True ? Que fait cet algorithme, finalement ?
• Quelle est sa complexité ?

2 Sommes doubles
Q 93. Écrire la fonction somme_double_1(n) qui prend un entier n > 1 en argument et qui renvoie la valeur de cette
somme :
Xn X n
ij
i=1 j=1

Tester (indication : somme_double_1(5) → 5699). Quelle est la complexité ?


Q 94. Écrire la fonction somme_double_2(n) qui prend un entier n > 1 en argument et qui renvoie la valeur de cette
somme :
Xn X n
i+j
i=1 j=1

Tester (indication : somme_double_2(12) → 1872). Quelle est la complexité ?


Q 95. Vérifier « expérimentalement » que :
n X
X n n
X
i+j =2·n· i
i=1 j=1 i=1

Indication : utiliser somme_double_2(), définir une autre fonction pour le membre de droite. Comparer les résultats
pour toutes les valeurs de n entre 1 et N (choisir N « pas trop grand » au départ pour ne pas que le temps de calcul
soit trop long).

3 Recherche des plus proches voisins


Q 96. Écrire la fonction proches_voisins(L) qui prend une liste d’au moins deux nombres en argument et renvoie les deux
valeurs les plus proches. Indication : comparer chaque valeur de la liste avec toutes les autres (combien cela fait-il de
boucles ?). Rappel : la fonction abs(), appliquée à un flottant ou un entier, renvoie la valeur absolue.
Tester. Quelle est la complexité ?

XII Lecture/écriture de fichiers


1 Commandes de base pour la lecture/écriture dans un fichier
Q 97. Copier le fichier data1.txt dans le même dossier que le fichier python que vous allez créer. Dans ce fichier python,
écrire et tester le code ci-dessous. Si l’ouverture du fichier échoue, il peut y avoir un problème d’encodage : essayer
d’ajouter l’argument encoding = 'utf8' à la fonction open (cela donne open('data1.txt', 'r', encoding = 'utf8')).
1 f = open('data1.txt', 'r')
2 a = f.read()
3 print(a)
4 f.close()
Info S1 – page 31/ 43
Noter la syntaxe pour ouvrir un fichier : open(nom_du_fichier, mode) ouvre le fichier dont le chemin d’accès est
donné par la chaîne de caractères nom_du_fichier. La chaîne de caractères mode indique si le fichier est ouvert en
lecture ('r' comme read) ou en écriture ('w' comme write). Remarque : par défaut, open ouvre un fichier en mode
texte, c’est-à-dire qu’on va lire ou écrire des chaînes de caractères dans le fichier 29 .
open renvoie ici un objet dont le type est un type fichier 30 . Cet objet est la structure de données présente en mémoire
qui fait le lien entre le code python et le fichier lui-même (sur le disque).
On accède au fichier en utilisant des méthodes (= des fonctions) associées à cet objet.
Noter la syntaxe pour fermer le fichier : on utilise la méthode .close(), ce qui donne ici f.close() (puisque dans
notre exemple cet objet s’appelle f).
Noter la syntaxe pour lire l’intégralité du fichier : f.read() renvoie une chaîne de caractères contenant l’intégralité
du fichier.
Q 98. Plutôt que d’utiliser open et close, on peut utiliser une syntaxe un peu différente :
1 with open('data1.txt', 'r') as f:
2 a = f.read()
3 print(a)
Il est plutôt recommandé d’utiliser cette syntaxe (qui ferme automatiquement le fichier lorsqu’on sort du bloc de code
qui suit le with). Noter la syntaxe :
with open(nom_du_fichier, mode) as variable_fichier:
bloc de code contenant
tout le code à exécuter
avec le fichier ouvert
Q 99. La méthode f.readline() renvoie une chaîne de caractères contenant une ligne du fichier. Essayer. Si on refait un
appel à f.readline(), cela renvoie la ligne suivante, etc. À la fin du fichier, f.readline() renvoie une chaîne de
caractères vide (= la chaîne ''). Cela permet d’envisager de lire un fichier ligne par ligne de la manière suivante
(essayer) :
1 with open('data1.txt', 'r') as f:
2 while True:
3 a = f.readline()
4 if a == '':
5 break # on force la sortie de la boucle while si la chaine renvoyée est vide
6 print(a)
Remarque : si le fait qu’il y ait dans l’affichage des « sauts de ligne » en plus par rapport à la question 97 vous gêne,
souvenez-vous que print() ajoute par défaut un retour à la ligne en plus de ce qu’on lui dit d’afficher.
Q 100. En fait, l’objet fichier permet de faire ceci de manière bien plus simple. En effet, il est itérable. Essayer :
1 with open('data1.txt', 'r') as f:
2 for a in f:
3 print(a)
Remarque : dans cet exemple, tout se passe comme si f était une liste de chaîne de caractères, comprenant un élément
pour chaque ligne du fichier, mais ce n’est en fait pas le cas.
Q 101. On récapitule ici quelques méthodes permettant de lire dans un fichier :
• .read() : retourne tout le fichier en une chaîne de caractères ;
• .read(n) : retourne une chaîne de caractères comprenant les n caractères suivants ;
• .readline() : retourne une chaîne de caractères comprenant la ligne suivante ;
• .readlines() : retourne une liste de chaîne de caractères contenant chacune une ligne du fichier.
Essayer celles qui n’ont pas encore été essayées.
Q 102. On récapitule ici quelques méthodes permettant d’écrire dans un fichier :
• .write(c) : écrit la chaîne de caractères c dans le fichier ;
29. Il est possible d’avoir un accès en mode binaire en ajoutant b dans la chaîne mode.
30. En fait, ici, le nom exact est _io.TextIOWrapper, on peut faire un type(f) dans la console pour le voir.
Info S1 – page 32/ 43
• .writelines(L) : L étant une liste de chaînes de caractères, écrit toutes les chaînes de caractères dans le fichier
(remarque : n’ajoute pas de retour à la ligne '\n' entre deux chaînes).
Essayer ces méthodes. Attention : il faut ouvrir un nouveau fichier en mode écriture (with open('data2.txt', 'w') as f:).
Attention aussi : si on ouvre un fichier existant, tout son contenu sera effacé ; si on souhaite ajouter des données à la
fin d’un fichier existant, il faut utiliser le mode 'a' (pour append, c’est-à-dire ajout).
Q 103. Tester le code :
1 t = []
2 temperature = []
3 with open('data1.txt', 'r') as f:
4 f.readline() # La 1ère ligne ne contient pas de données : on ne garde pas.
5 for a in f:
6 x, y = a.split()
7 t.append(float(x))
8 temperature.append(float(y))
9 print(t)
10 print(temperature)

• Remarquer la méthode .split(), qui permet de découper une chaîne de caractères en plusieurs chaînes de
caractères (le séparateur par défaut est le caractère espace).
• Remarquer aussi qu’on force un changement de type. En effet, la fonction float() renvoie son argument après
l’avoir converti en un flottant. Il existe aussi int() pour convertir en entier, str() pour convertir en chaîne de
caractères, bool() pour convertir en booléen. . .

2 Répertoire courant
• Par défaut, open() cherche le fichier à ouvrir dans le répertoire (= le dossier) courant, c’est-à-dire celui où est
enregistré le fichier python en train d’être exécuté. Si besoin, on peut spécifier le chemin complet du fichier. Exemple :
open('U:/tppython/sousdossier/fichier.txt'). Ou bien on peut utiliser un chemin relatif (= un chemin donné
« à partir » du répertoire courant) : open('sousdossier/fichier.txt').
Remarque : il est préférable d’utiliser des slashs / plutôt que des antislashs \ 31 . Attention, si malgré tout on veut
utiliser les \, il faut écrire U:\\Dossier\\SousDossier et pas U:\Dossier\SousDossier.
• La fonction os.getcwd() (du module os = operating system = système d’exploitation) permet d’obtenir le répertoire
courant (getcwd = get current working directory = obtenir le répertoire de travail courant).
• La fonction os.chdir() (= change directory = changer de répertoire) prend comme argument une chaîne de caractères
correspondant à un chemin de répertoire et définit ce répertoire comme le répertoire de travail courant.

3 Premiers exercices
Q 104. Le fichier premiers_1000.txt est fourni. Il contient les 1000 premiers nombres premiers. Écrire un programme qui
récupére le 684ème nombre premier et le stocke dans une variable de type int. Indication : c’est 5113.
Q 105. Écrire un programme qui détermine la somme des 1000 premiers nombres premiers. Indication : c’est 3 682 913.
Q 106. Le fichier tennis_femmes_2022.csv contient le classement mondial des joueuses de tennis courant 2022, avec les
informations suivantes : rang, nom, prénom, nationalité, nombre de points. Écrire un programme déterminant :
• le nombre de joueuses françaises de ce classement (indication : 75) ;
• la moyenne des scores de toutes les joueuses (indication : environ 178) ;
• la moyenne des scores des 100 premières joueuses (indication : environ 1420) ;
• la moyenne des scores des joueuses françaises (indication : environ 169) ;
• la moyenne des rangs des joueuses dont le prénom commence par un N (indication : environ 761).
Indication : utiliser la méthode .split(), avec en argument le bon caractère. Utiliser aussi l’argument encoding = 'utf8'
pour open.
31. Car cela produit du code (un peu) plus portable d’un système à l’autre.
Info S1 – page 33/ 43
Q 107. Le fichier admissibles.txt contient la liste des élèves de PT qui se sont présentés au concours. Il contient des lignes
de la forme :
1234 TOURNESOL Tryphon Admissible 2
56789 HADDOCK Archibald Eliminé -
La première valeur est le numéro du candidat et la dernière valeur est le numéro de la série d’oral du candidat (il y
a 4 séries d’oral) quand il est admissible, et - sinon. Le caractère de séparation des quatre champs d’une ligne est le
caractère tabulation \t.
Écrire un code produisant une liste de 6 entiers : le nombre de total de candidats, le nombre d’admissibles et le nombre
de candidats dans chaque série. Contrainte : parcourir la liste une seule fois.
Indication : avant d’utiliser la méthode .split(), utiliser la méthode .strip() sur chaque ligne : elle « nettoie »
(= élimine les espaces, \n. . .) le début et la fin de la chaîne de caractères.

4 Loi de Benford (et tracé d’histogramme)


• On cherche à vérifier la loi de Benford (voir fr.wikipedia.org/wiki/Loi_de_Benford). Si on suit une certaine intuition,
on peut penser que la probabilité qu’un nombre pris au hasard commence par un certain chiffre est la même quel
que soit ce chiffre. C’est vrai, par exemple, pour des nombres tirés au hasard (avec une distribution uniforme) entre
1 et 10000. Mais, pour un certain nombre de séries de nombres ce n’est pas le cas : la loi de Benford indique des
proportions qui ne sont pas les mêmes.
• Le fichier balance_commune_2018_court.csv contient les soldes des comptes de différentes organisations. Sur
chaque ligne du fichier on trouve, séparés par des virgules : le nom de la commune, le code INSEE (du département
ou de la commune), le numéro du compte, le solde du compte (qui peut être positif ou négatif).
Q 108. Écrire un programme qui, à partir du fichier, crée une liste d’entiers liste_chiffre, contenant le premier chiffre non
nul de chaque solde.
• On souhaite construire un histogramme de ces premiers chiffres. Dans un histogramme, on porte en abscisse la
grandeur étudiée (ici le premier chiffre p). Cet axe est découpé en intervalles appelés « classes » (bins en anglais). Ici
cet intervalle vaut 1. En ordonnée, on trouve le nombre d’occurrences des valeurs de p dans l’intervalle correspondant.
La fonction plt.hist(A, bins = 'rice') du module matplotlib.pyplot (importé sous l’alias plt) réalise un
histogramme des valeurs contenues dans la liste A, en déterminant une largeur des classes (= bins) optimale 32 . Ne
pas oublier le plt.show() après.
Q 109. Compléter le programme pour :
• tracer l’histogramme de la série statistique contenue dans liste_chiffre ;
• obtenir une liste (ou un tableau) L de la forme [n1, ... , n9], où ni est le nombre de nombres commençant
par le chiffre i
• en déduire les proportions correspondantes.
Comparer ces proportions à celles prédites par la loi de Benford.

XIII Algorithmes gloutons


• Les algorithmes gloutons (greedy algorithms en anglais) sont utilisés pour résoudre certains problèmes d’optimisation.
Un problème d’optimisation consiste à déterminer les valeurs de paramètres permettant :
* de minimiser ou maximiser une fonction objectif ;
* éventuellement en satisfaisant une ou des fonctions contraintes 33 .
Exemples classiques de problèmes d’optimisation : le rendu de monnaie, le problème du sac à dos, le problème du
voyageur de commerce. . .
• À chaque étape d’un algorithme glouton, on effectue le choix qui paraît optimal (= qui réduit le plus l’écart entre
le résultat souhaité et le point de départ) à cette étape, sans se soucier de la suite du problème. On passe ensuite à
l’étape suivante, etc.
• Les algorithmes gloutons fournissent une solution, mais ce n’est pas forcément la meilleure possible. Ils sont cependant
relativement faciles à mettre en œuvre.
32. Avec la méthode de Rice.
33. Le problème peut être avec ou sans contrainte.
Info S1 – page 34/ 43
1 Rendu de monnaie
• Exemple : vous devez payez 86 €. Vous donnez un billet de 200 €. Il faut rendre 200 - 86 = 114 €. On cherche la
façon optimale de faire cela, au sens où on veut rendre 114 € en utilisant le moins possible de pièces/billets. Le fait
que ce soit un rendu de monnaie n’a aucune importance ; il s’agit juste, étant donnée une somme d’argent, de trouver
une combinaison de pièces/billets permettant de totaliser cette somme (en utilisant le moins possible de pièces/billets
si on veut une solution optimale).
* Le commerçant commence par rendre un billet de 100 €. C’est le choix qui paraît le meilleur, au sens où c’est
celui qui minimise le plus possible (parmi les pièces/billets disponibles) la somme à rendre à l’étape suivante. Dit
autrement, c’est la pièce/billet la plus grande possible qu’on puisse rendre.
* Il reste 114 - 100 = 14 € à rendre. Le commerçant rend alors un billet de 10 €.
* Il reste 14 - 10 = 4 €. Il rend 2 €.
* 4 - 2 = 2 €. Il rend (encore) 2 €.
* 2 - 2 = 0. C’est fini. . .
Remarque : dans la vraie vie, le commerçant ne fait pas comme ça, mais exactement dans l’ordre inverse 34 .
• On se dote de la liste des pièces/billets, appelée système :
S = [500, 200, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01]
Remarque : les valeurs dans le système sont classées par ordre décroissant. C’est indispensable pour le bon fonction-
nement de l’algorithme.
On note v la valeur à rendre (qui va diminuer au cours de l’avancement de l’algorithme). On suppose que cette valeur
a déjà été calculée. On note R la liste des pièces/billets à rendre.
• Fonctionnement de l’algorithme :
1. On compare v au premier élément de S (c’est-à-dire S[0]). Si v est plus petit que S[0], alors on passe à l’élément
suivant, jusqu’à arriver au premier élément (numéro i) tel que v soit plus grand que S[i]. Si même le dernier
élément de S est trop grand, alors l’algorithme se termine.
2. On ajoute ensuite la valeur de ce dernier élément à la liste L des pièces/billets à rendre, et on retranche cette
valeur à v.
3. On recommence jusqu’à ce que v soit inférieur au plus petit élément de S (comme déjà dit à l’étape 1).
• En supposant que la valeur à rendre est 114 €, vérifier que le tableau ci-dessous correspond bien à cet algorithme :
i v (en début d’itération) S[i] v (en fin d’itération) L
0 114 500 sans objet []
1 114 200 sans objet []
2 114 100 14 [100]
0 14 500 sans objet [100]
1 14 200 sans objet [100]
2 14 100 sans objet [100]
3 14 50 sans objet [100]
4 14 20 sans objet [100]
5 14 10 4 [100, 10]
0 4 500 sans objet [100, 10]
.. .. .. .. ..
. . . . .
4 4 20 sans objet [100, 10]
5 4 10 sans objet [100, 10]
6 4 5 sans objet [100, 10]
7 4 2 2 [100, 10, 2]
0 2 500 sans objet [100, 10, 2]
.. .. .. .. ..
. . . . .
6 2 5 sans objet [100, 10, 2]
7 2 2 0 [100, 10, 2, 2]
Remarque : les étapes en italiques ne servent à rien, on pourrait se débrouiller pour les sauter (mais attention, après
avoir déterminé qu’il faut rendre un billet de 10 €, il faut à nouveau se demander s’il faut rendre un autre billet de
10 €, et pas passer à 5 € directement).
34. Il commence par donner 2 € en comptant « 86 + 2 = 88 € », puis encore 2 € en comptant « 88 + 2 = 90 € », puis « 90 + 10 =
100 € » etc. jusqu’à arriver à 200 €. Mais l’algorithme glouton, lui, commence bien par la plus grande des pièces/billets.
Info S1 – page 35/ 43
Q 110. Écrire une fonction le_plus_grand_a_rendre(v, systeme), qui prend en argument la valeur à rendre v et le
système utilisé systeme, et renvoie le plus grand élément de systeme qu’on puisse rendre. Si aucun élément ne
convient, la fonction renvoie le booléen False.
Q 111. Écrire une fonction rendu_monnaie(v, systeme), qui prend en argument la valeur totale à rendre v et le système
utilisé systeme, et renvoie la liste des pièces/billets à rendre. Si on ne peut rien rendre, la fonction renvoie une liste
vide []. Indication : cette fonction fait appel à la fonction le_plus_grand_a_rendre().
Essayer cette fonction avec le système « euro » donné plus haut, pour rendre 114 €, puis pour 77,24 €.
Q 112. Essayer aussi avec 77,21 €. Quel est le problème (ajouter des print pour voir ce qu’il se passe) ? Modifier la fonction
rendu_monnaie() pour régler ce problème. Indication : utiliser la fonction round(number, ndigits=None).
Q 113. Avant le 15 février 1971, le pound (= la livre sterling) britannique était divisé en 20 shillings, chaque shilling étant
divisé en 12 pence (pluriel de penny). Si on exprime les valeurs des différentes pièces qui existaient en pence, cela
donne le systeme S2 = [240, 120, 60, 30, 24, 12, 6, 3, 1] 35 .
Essayer la fonction rendu_monnaie() avec ce système, pour rendre 48 pence. Dans ce cas, est-ce que l’algorithme
glouton renvoie la solution optimale ? Quelle serait la solution optimale ?
• On voit que l’algorithme glouton renvoie ici une solution, mais qui n’est pas la solution optimale. Un tel algorithme
est parfois appelé une heuristique.
• Dans le cas du système « euro », la solution renvoyée par l’algorithme glouton est toujours optimale. Cela est du au
fait que les valeurs des pièces/billets ont été « bien choisies ». Un tel système est appelé un système canonique.
L’ancien système britannique n’est pas un système canonique.

2 Allocation de ressources
• On souhaite trouver une solution, grâce à un algorithme glouton, à un problème d’allocation d’une ressource en réponse
à plusieurs demandes. On prend l’exemple de l’attribution de créneaux horaires pour l’utilisation d’une salle sur une
journée. Plusieurs demandes de réservation sont faites, chacune avec une heure de début et une heure de fin. Exemple
de liste de demandes de réservation : [[12.15, 14, 'a'], [13, 14, 'b'], [7, 13, 'z'], [15, 17, 'r']]
pour 4 demandes de réservations (de 12h15 à 14h, de 13h à 14h, etc.). La chaîne de caractère ('a', 'z'. . .) sert à
identifier une réservation. Elle n’est pas indispensable ici. L’objectif est de trouver une solution optimale, c’est-à-dire
de satisfaire le maximum de demandes.
• Quelques précisions :
* la salle ne peut être utilisée qu’une fois à un instant donné ;
* il n’y a pas besoin de prévoir de période de battement entre deux réservations ;
* on cherche à privilégier le nombre de réservations plutôt que la durée totale de réservation : par exemple, on
préfère réserver la salle trois fois une heure plutôt que une fois cinq heures, ou même que une fois trois heures.
• Pour chercher à répondre au problème avec un algorithme glouton, il faut choisir quel critère « glouton » utiliser. En
effet, on peut en envisager plusieurs, par exemple :
* durée croissante : on considère les réservations par ordre de durée croissante ;
* durée décroissante : on considère les réservations par ordre de durée décroissante ;
* début croissant : on considère les réservations par ordre chronologique de leur début ;
* fin croissante : on considère les réservations par ordre chronologique de leur fin ;
* le moins de conflits : on considère les réservations par ordre croissant de leur nombre de conflits, un conflit étant
ici le fait que deux réservations portent sur le même créneau horaire.
Q 114. On choisit le critère de fin croissante. La fonction tri_fin_croiss(L) est fournie (dans le fichier allocation_de_salle.py).
Elle prend en argument une liste L de demandes de réservations, et renvoie une nouvelle liste, où ces demandes sont
triées par date de fin croissante. Remarque : on verra plus tard dans l’année différents algorithmes de tri. Écrire la
fonction glouton_fin_croiss(L), qui prend en argument une liste de demandes de réservation L (non triée), et
renvoie une nouvelle liste, contenant les réservations acceptées, dans l’ordre chronologique.
35. Dans l’ordre : pound ou sovereign (240), half sovereign (120), crown (60), half crown (30), florin (24), shilling (12), sixpence (6), threepence
(3), penny. Et encore, il manque : guinea (252), double florin (48), halfpenny (0,5), farthing (0,25), 1/2 farthing (0,125), 1/3 farthing (≃ 0,083).
Voir fr.wikipedia.org/wiki/Livre_sterling.
Info S1 – page 36/ 43
Optimalité de l’algorithme glouton avec le critère de fin croissante
• On peut montrer que l’algorithme glouton avec le critère de fin croissante (earliest finish time) fournit une solution
optimale (c’est-à-dire qui permet d’accepter le maximum de demandes de réservation). Pour information, voici cette
preuve.
• Tout d’abord, il est certain qu’il existe au moins une solution optimale au problème (démonstration triviale, par
l’absurde). On la note S. Son cardinal est k. Ses éléments sont les {s1 , s2 · · · sk }. On note d(si ) et f (si ) les dates de
début et de fin de l’élément numéro i.
• L’algorithme glouton avec heure de fin croissante fournit une solution notée G. Son cardinal est ℓ. Ses éléments sont
notés {g1 , g2 . . . gℓ }.
• Tout d’abord, on montre qu’il existe une solution optimale T , de (de cardinal ℓ, donc) telle que les k premiers éléments
de T sont les mêmes que les k éléments de G (i.e. ∀i ∈ J1, jK, gi = ti ). On fait une démonstration par récurrence. La
propriété Pj est « il existe une solution optimale T telle que les j premiers éléments de T et de G sont identiques »
(j ≤ ℓ).
* Initialisation (P1 ) : S étant une solution optimale, on a :

d(s1 ) ≤ f (s1 ) ≤ d(s2 ) ≤ f (s2 ) ≤ · · · ≤ d(sk ) ≤ f (sk )

Puisque G est une solution gloutonne avec heure de fin croissante, alors on a nécessairement f (g1 ) ≤ f (s1 ).
Donc g1 est compatible avec s2 (i.e. f (g1 ) ≤ d(s2 )) puisque f (s1 ) ≤ d(s2 ).
Donc l’ensemble T = {g1 , s2 , s3 · · · sk } est une solution optimale dont le premier élément est commun avec G.
P1 est vérifiée
* Propagation (Pj =⇒ Pj+1 ) : l’ensemble {g1 , · · · , gj , sj+1 , · · · , sk } vérifie Pj .
On a f (gj ) ≤ d(gj+1 ) (car G est une solution).
On a aussi f (gj+1 ) ≤ d(sj+2 ) car f (gj+1 ) ≤ f (sj+1 ) (car sj+1 est une demande dont la date de fin est minimale
dans l’ensemble des demandes auquel on a enlevé les demandes g1 à gj ) et f (sj+1 ) ≤ d(sj+2 ) (car S est une
solution).
Donc gj+1 est compatible avec gj et sj+2 .
Donc l’ensemble {g1 , · · · , gj+1 , sj+2 , · · · , sk } vérifie bien Pj+1 .
* Conclusion de la récurrence : il existe une solution optimale T = {g1 , · · · , gk , sk+1 , · · · , sℓ } dont les k premiers
éléments sont les mêmes que G.
• Il reste à montrer que k = ℓ (donc que G est optimale). Par l’absurde : on suppose k < ℓ. On remarque alors que
tk+1 est une demande compatible avec les demandes précédentes de G. Donc l’algorithme qui a fournit G n’aurait
pas du s’arrêter. C’est une contradiction.
Donc k = ℓ. Donc G est optimale.

Questions bonus : autres critères


Q 115. On fournit la fonction tri_duree_croiss{L}, qui prend en argument une liste de demandes de réservation et renvoie
une nouvelle liste, triée par durée croissante. Écrire une fonction glouton_duree_croiss(L) qui prend en argument
une liste de demandes de réservation L (non triée), et renvoie une nouvelle liste, contenant les réservations acceptées,
dans l’ordre chronologique. Tester et trouver un contre-exemple qui montre que ce critère permet pas de trouver la
solution optimale.
Q 116. Refaire le même travail avec le critère début croissant (il faut commencer par écrire la fonction de tri, en s’inspirant
des précédentes). Tester avec [[0, 6], [1, 3], [3, 4], [4, 6]].
Q 117. De même avec le critère le moins de conflits. Tester avec [[0, 2], [2, 4], [4, 6], [6, 8], [1, 3], [1, 3],
[1, 3],[3, 5], [5, 7], [5, 7], [5, 7]].

3 Problème du sac à dos


a Version fractionnaire
• Un promeneur souhaite transporter dans son sac à dos le fruit de sa cueillette. On a les contraintes suivantes :
* La capacité maximale du sac à dos est 5 kg, mais la masse totale cueillie est strictement supérieure à 5 kg.
Info S1 – page 37/ 43
* Les fruits cueillis ont des valeurs au kg différentes.
* Le promeneur souhaite maximiser la valeur de son chargement.
* Récapitulatif des quantités cueillies et des prix unitaires :
Fruit quantité prix unitaire
framboise 1,2 kg 24 €/kg
myrtille 3 kg 16 €/kg
fraise 5 kg 6 €/kg
mûre 5 kg 3 €/kg
• Remarque : on peut prélever une quantité arbitraire de fruit dans la quantité disponible. Dit autrement, la masse d’une
seule framboise est suffisamment petite pour qu’on puisse prélever une masse choisie arbitrairement entre 0 et 1,2 kg.
Vocabulaire : on parle de la version fractionnaire de l’algorithme du sac à dos.
• Principe de l’algorithme : on place d’abord dans le sac la quantité maximale de fruit les plus chers par unité de masse.
S’il y a encore de la place, on continue avec les fruits les plus chers par unité de masse restant, etc.
Q 118. Écrire une fonction sac_a_dos(cueillette, capacite). Elle prend en argument le flottant capacite, qui est la ca-
pacité du sac à dos en kg. Elle prend aussi en argument la liste cueillette, qui représente la cueillette. Exemple de dé-
finition de cette liste : cueillette = [['framboise', 1.2, 24], ['myrtille', 3, 16], ['fraise', 5, 6],
['mûre', 5, 3]]. On suppose que cette liste est déjà triée par ordre de prix au kg décroissant. La fonction renvoie
une liste décrivant le contenu optimal du sac à dos (exemple : [['fraise', 0.3], ['myrtille', 2] si le sac à
dos doit contenir 0,3 kg de fraise et 2 kg de myrtille). Elle renvoie aussi la valeur, en euros, du contenu du sac.
• On voit (on peut le montrer) que ce problème du sac à dos trouve ici une solution optimale par une méthode gloutonne.
On va voir que ce n’est pas le cas pour une autre variante de ce problème, dite non fractionnaire.

b Version non-fractionnaire
• On a les fruits suivants :
Fruit quantité masse d’un fruit prix au kg
melon charentais 1 fruit 1,1 kg 4 €/kg
melon jaune 1 fruit 1,9 kg 2,5 €/kg
pastèque 2 fruits 3 kg 2 €/kg
• Cette fois, les éléments à transporter ne sont pas fractionnables. Par exemple, si on prend de la pastèque, c’est soit 0
kg, soit 3 kg (= 1 pastèque), soit 6 kg (= 2 pastèques), etc.
Q 119. Écrire une fonction sac_a_dos_nonfrac(cueillette, capacite) similaire à la précédente, sauf que :
• cueillette est par exemple de la forme [['melon charentais', 1, 1.1, 4], ['melon jaune', 1, 1.9, 2.5],
['pastèque', 2, 3, 2]] ;
• elle ne renvoie pas des masses mais des nombres de fruits.
Indication : on peut copier-coller le code de la fonction précédente et le modifier (beaucoup).
Q 120. Considérer la solution [['pastèque', 1], ['melon jaune', 1]]. La solution obtenue par l’algorithme à la ques-
tion précédente est-elle optimale ?
• En fait, cet algorithme glouton n’est pas adapté pour trouver une solution optimale du problème du sac à dos non
fractionnaire. Il présente bien la propriété de sous-structure optimale : la solution d’un sous-problème (= le choix
fait à une étape du problème) est bien optimal (c’est normal, c’est un algorithme glouton). Mais les choix gloutons
successifs n’engendrent pas une solution optimale globalement.
• Pour répondre de manière optimale à la question, il faudrait étudier toutes les combinaisons possibles, de préférence
de manière «efficace » : voir la programmation dynamique en deuxième année.

XIV Manipulation d’images


1 Quelques éléments sur le codage numérique d’une image
• Il existe deux grandes catégories de d’image numérique :
Info S1 – page 38/ 43
* les images matricielles (ou bitmap) : on a une « grille » de pixels, et on associe à chaque pixel une ou des
propriétés (couleur, luminance. . .).
Exemples de format : bmp, gif, png, jpeg
* les images vectorielles : on décrit l’image sous une forme « mathématique », par exemple « un segment noir
d’épaisseur 1 mm de tel point à tel autre point. » 36
Exemples de format : svg, ps, pdf (même si un pdf peut « inclure » une image bitmap).
• On s’intéresse ici à des images bitmap.
• On décrit de manière simplifiée l’affichage d’une image sur un écran. Chaque pixel de l’écran comprend plusieurs
sources lumineuses, de couleurs différentes : une rouge, une verte, une bleue (système RVB, en anglais RGB). Les
différentes couleurs sont réalisées par synthèse additive de ces trois couleurs primaires, en allumant de manière plus
ou moins intense les trois sources 37 .
• Dans ce système, on peut faire le choix (que nous faisons ici) de coder chacune des trois composantes d’un pixel sur
un octet. Il faut donc 3 octets par pixel : un pour le rouge, un pour le vert, un pour le bleu. Un nombre entier codé
sur un octet peut prendre des valeur comprises entre 0 et 255.
Précisions : Un octet = 8 bits. Chaque bit ne peut prendre que 2 valeurs : soit 0, soit 1. Donc sur un octet, on peut
coder 28 = 256 valeurs différentes. Par exemple, on peut coder un entier compris entre 0 et 255.
• Exemples :
* le triplet (0, 0, 0) code le noir.
* (255, 255, 255) code le blanc.
* (255, 0, 0) code le rouge, (0, 255, 0) code le vert, (0, 0, 255) code le bleu.

2 Premières manipulations d’image


Q 121. On utilise les sous-modules image et pyplot du module matplotlib. On fournit le fichier image logo.bmp. Essayer
le code :
1 import matplotlib.pyplot as plt
2 import matplotlib.image as img
3

4 image = img.imread('logo.bmp')
5 plt.imshow(image)
6 plt.show()
Noter l’utilisation de la fonction img.imread() : elle prend en argument une chaîne de caractères contenant le nom
du fichier image à charger. Elle renvoie un tableau (un np.ndarray du module numpy), stockée ici dans la variable
image.
Noter aussi la fonction plt.imshow() qui prend en argument un np.ndarray(dont on va voir la forme ci-dessous),
et affiche l’image (ne pas oublier le plt.show()).
Passer la souris au-dessus de l’image. On voit que les coordonnées du pointeur de la souris s’affichent. On voit de plus
le contenu du pixel correspondant : un tableau à une dimension de 3 éléments, contenant les valeurs R, V et B pour
ce pixel.
Remarquer aussi que le pixel de coordonnées (0,0) est en haut à gauche.
Q 122. Ajouter au début du code la ligne import numpy as np, de manière à disposer des outils du module numpy pour
manipuler les images en mémoire (puisque Python les stocke sous la forme de np.ndarray). Ajouter ce code à la
suite du précédent (il faut aussi commenter le plt.show(), ou le déplacer en fin de code) :

7 print(image) # Affiche tout le tableau : ça peut être long,


8 # mais l'affichage est automatiquement raccourci.
9 print(type(image))
10 print(np.shape(image)) # Ou bien utiliser la méthode : print(image.shape).
11 print(type(image[0, 0, 0]))
36. Remarque : dans ce cas, on peut zoomer autant que souhaité, l’image ne sera jamais pixelisée, car on recalcule les pixels à afficher à chaque
changement de niveau de zoom.
37. Remarque : en fait l’espace de couleur RGB est plus restreint que ce qu’un œil humain peut voir. Il n’arrive notamment pas à reproduire
les bleus ou les verts « purs » par exemple.
Info S1 – page 39/ 43
On constate que image est bien un tableau numpy. C’est un tableau à trois dimensions puisque image.shape renvoie
un tuple de 3 éléments. Dans cet exemple, c’est un tableau de forme (= shape) (46, 46, 3). On peut voir cela comme
un tableau à deux dimensions (46 lignes × 46 colonnes), correspondant à une image de 46 × 46 pixels, dont chaque
élément contient un tableau à une dimension de 3 éléments (les trois composantes R, V, B du pixel).
On constate aussi que chaque élément du tableau est un entier codé sur 8 bits : en effet, type(image[0, 0, 0]
renvoie numpy.uint8.
Q 123. À l’aide de la fonction np.zeros, créer un tableau à trois dimensions, de forme 20 × 30 × 3, contenant uniquement
des 0. Ce tableau représente donc une image de 20 lignes par 30 colonnes, complètement noire.
Rappel : np.zeros prend en argument un tuple comprenant le même nombre d’éléments que celui de dimensions
souhaitées pour le tableau.
Indication : pour que chaque élément soit un entier codé sur 8 bits, ajouter aux arguments de np.zeros l’argument
optionnel dtype = np.uint8.
Q 124. Afficher l’image pour vérifier. Modifier la valeur des 3 premiers pixels de la première ligne pour qu’ils soient rouge
([255, 0, 0]), vert ([0, 255, 0]) et bleu ([0, 0, 255]). Afficher.
Q 125. Sauvegarder l’image en utilisant la fonction img.imsave('nom_du_fichier.bmp', tableau_numpy), où les argu-
ments sont une chaîne de caractères contenant le nom du fichier souhaité, et le tableau numpy contenant l’image à
enregistrer. Ouvrir l’image avec un logiciel quelconque pour vérifier. . .
Q 126. Créer une image blanche en utilisant imageblanche = 255 * np.ones((20, 40, 3), dtype = np.uint8). Af-
ficher.
Remarque : si on souhaite faire plusieurs figures, chacune avec une image, il faut utiliser plusieurs fois img.imshow(),
en ayant à chaque fois utilisé un plt.figure('Figure 1') avant (avec un nouvel argument à chaque fois).

3 Symétrie et rotation « simples »


Q 127. Écrire une fonction symetrie_verticale(im) qui prend en argument un tableau numpy im représentant une image,
et renvoie une nouvelle image, symétrique de la première par rapport à un axe vertical (passant au milieu de l’image).
Il ne faut pas modifier l’image d’origine.
Indication : si image est un tableau numpy représentant une image en mémoire, alors image.shape[0] renvoie
le nombre de lignes, image.shape[1] renvoie le nombre de colonnes, et image.shape[2] renvoie le nombre de
composantes par pixel (c’est-à-dire 3).
Tester sur logo.bmp, red_pill.bmp, ou autre chose !
Q 128. Écrire une fonction quart_de_tour(im) renvoyant une nouvelle image, la même que celle donnée en argument mais
pivotée de + π2 .
Remarque : attention, la forme de la nouvelle image n’est pas la même que celle de l’ancienne.
Tester.

4 Conversion en niveaux de gris


Q 129. On propose, pour convertir une image en couleur en niveaux de gris, d’attribuer à chaque composante d’un pixel la
valeur moyenne des trois composantes de ce pixel. Écrire un fonction niv_gris_moy(im) qui renvoie une nouvelle
image, résultat de la conversion en niveaux de gris de l’image prise en argument. Tester.
Indication : Si vous rencontrez l’avertissement (warning) overflow encountered in ubyte_scalars, c’est proba-
blement que vous additionnez (ou faites une autre opération) deux entiers de type np.uint8, et que le résultat de
l’opération est plus grand que 255 (c’est-à-dire la valeur maximale qu’on peut stocker dans un np.uint8). Il ne peut
donc pas être stocké dans un np.uint8. Il y a ce qu’on appelle un dépassement de capacité (overflow). Il faut
donc forcer la conversion du type d’au moins un des deux nombres impliqués dans l’opération en un type ayant une
« capacité » plus grande, par exemple int ou float. Par exemple, on peut utiliser float(a) + b au lieu de a + b
(où a et b sont deux np.uint8).
Q 130. Cette méthode de conversion en niveaux de gris d’une image en couleurs n’est pas toujours convaincante. En effet,
les différentes couleurs ne sont pas perçues de la même façons par l’œil. Une norme, éditée par la Commission
Internationale de l’Éclairage (CIE), recommande qu’une composante d’une image en niveaux de gris soit calculée par
une moyenne pondérée des trois composantes R, V et B : G = 0, 2125 · R + 0, 7154 · V + 0, 0721 · B. Écrire un fonction
niv_gris_CIE(im) qui renvoie une nouvelle image, résultat de la conversion en niveaux de gris de l’image prise en
argument, utilisant cette recommandation.
Info S1 – page 40/ 43
5 Détection de contour
• Un intérêt de détecter les contours des objets présents sur une image est par exemple de procéder ensuite à la
reconnaissance de cet objet (en reconnaissant sa forme), ou bien de pouvoir suivre son mouvement. On étudie ici
sommairement la détection de contour.
• Un contour est la limite d’un objet dans une image. Cette limite est caractérisée par une variation brutale de couleur
ou de contraste. Dans cette optique, pour un pixel donné, on peut se demander : est-il semblable, de même couleur
que ses voisins ? sinon, quelle est la différence entre lui et ses voisins ? cette différence est-elle suffisamment grande
pour qu’il s’agisse de la limite d’un objet ? dit autrement, c’est combien, « suffisamment grande » ?
• Pour chaque pixel, on calcule une distance avec son voisinage, puis on décide si ce pixel est sur un contour en effectuant
un seuillage : si la distance est supérieure au seuil, alors le pixel est situé sur un contour. On crée une nouvelle image,
de mêmes dimensions que celle d’origine, où tous les pixels sont noirs sauf ceux des contours, qui sont blancs.
• Il reste deux questions à régler :
* comment calculer une distance : norme euclidienne, maximum des différences avec les voisins, une autre norme ?
* jusqu’où étendre le voisinage : une rangée de voisins (i.e. les 8 pixels les plus proches), 2 rangées (i.e. les 24
pixels les plus proches) ? plus ? une partie seulement de ces 8 ou 24 pixels ?
• On propose ici un algorithme simple. La distance est calculée en faisant la moyenne des valeurs absolues des différences
entre la valeur du pixel et celles des 8 plus proches voisins. Cela donne la relation suivante, où d(i, j) est la distance
entre la valeur p(i, j) d’un pixel de coordonnées (i, j) et les valeurs de ses 8 plus proches voisins :
i+1 j+1
1 X X
d(i, j) = p(i′ , j ′ ) − p(i, j)
8 ′
i =i−1 j ′ =j−1

Remarque : inutile d’exclure de la somme le terme tel que i = i′ et j = j ′ , car il est nul.
• La sous-matrice suivante, extraite de l’image globale, est constituée du pixel considéré plus ses 8 voisins :
 

 pi−1,j−1 pi−1,j pi−1,j+1  
pi,j−1 pi,j pi,j+1 

 

 

 
pi+1,j−1 pi+1,j pi+1,j+1

Chaque pixel pi′ ,j ′ comprend trois composantes (R, V, B).


On appelle cette sous-matrice matrice locale.
Q 131. Écrire une fonction distance_p(mloc) qui prend en argument une matrice locale (extraite d’une image en niveaux
de gris), et renvoie la distance du pixel central de cette matrice locale à ses voisins, en suivant la relation donnée
ci-dessus. Cette matrice locale est sous la forme d’une np.ndarray à trois dimensions, de format 3 × 3 × 3.
Remarque : il suffit de travailler sur une des composantes R, V ou B car elles sont égales (on travaille avec des images
en niveaux de gris).
Q 132. Écrire une fonction contour(image:np.ndarray, seuil) -> np.ndarray, np.ndarray qui prend en argument
une image sous la forme d’un np.ndarray à trois dimensions, et renvoie deux np.ndarray (qui correspondent à
des images). Cette fonction convertit l’image en niveaux de gris (avec niv_gris_CIE()) et place le résultat dans
une nouvelle variable. Ensuite, elle crée deux nouvelles images de mêmes dimensions que la première (im_contour et
im_distance). im_distance contient les distances calculées pour chaque pixel (et 0 ailleurs). im_contour contient
le résultat du seuillage (les pixels où distance > seuil sont blancs, les autres noirs). Ces deux nouvelles images sont
renvoyées. Tester sur logo.bmp, sur red_pill.bmp : il faut ajuster la valeur de seuil.

6 Application d’un filtre quelconque à une image


• On considère la matrice F , de taille n × n, où n est un entier impair. Cette matrice est le filtre, ou masque, qu’on va
appliquer sur l’image, en réalisant ce qu’on appelle un produit de convolution. Par exemple, pour réaliser une opération
de moyennage-floutage, on peut utiliser ce filtre :
 
1 1 1
1  
 
F = · 1 1 1 
9   


1 1 1

Un élément de F est noté fi,j . Sur cet exemple, n = 3.


Info S1 – page 41/ 43
• On considère également une image comportant Nli lignes et Ncol colonnes, chaque pixel comportant trois compo-
santes R, V et B. En python, on utilise un np.ndarray à trois dimensions, de format Nli×Ncol×3. Pour décrire le
fonctionnement de l’opération de filtrage, on considère une matrice de taille Nli×Ncol, représentant une des trois
composantes R, V ou B de l’image. On note cette matrice M . Un élément de M est noté mi,j .
• Le résultat de l’opération de filtrage (= masquage = produit de convolution) est la matrice N . Elle a la même taille
que M . Un élément de N est noté ni,j . N est définie ainsi :
* On définit k =
2 (partie entière de 2 , dit autrement le résultat de la division entière de n par 2). On définit
n n

les éléments du bord d’une matrice comme ceux situés sur les k premières et les k dernières lignes et colonnes.
Les éléments du bord de N sont identiques aux éléments des bords de M 38 .
* Pour tous les éléments qui ne sont pas sur les bords, on calcule ni,j de la manière suivante :

i+k
X j+k
X
ni,j = fi′ ,j ′ · mi′ ,j ′
i′ =i−k j ′ =j−k

Dit autrement : on effectue le produit terme à terme 39 des éléments du filtre F par les éléments du voisinage
du pixel concerné (F et ce voisinage ayant tous les deux une taille n × n).
Remarque : on voit bien qu’on ne peut pas calculer les ni,j sur les bords avec cette relation.
Q 133. Écrire une fonction filtrer(image:np.ndarray, filtre:np.ndarray) -> np.ndarray. Elle prend en argument
deux np.ndarray, qui sont une image et un filtre. Elle crée une nouvelle image, résultat de l’opération de filtrage
décrite ci-dessus, et la renvoie.
Rappel et indication : Si A et B sont deux np.ndarray de même format, alors A*B renvoie bien un tableau de même
format, où on a effectué un produit terme à terme 40 . De plus, np.sum(A) renvoie la somme des éléments de A.
• On peut tout à fait imaginer des filtres pour lesquels les valeurs obtenues seraient en dehors de l’intervalle possible
(i.e. moins de 0 ou plus de 255). Pour éviter cette situation, on peut imaginer trois stratégies :
* La première, qui est celle qu’on a utilisé ci-dessus, consiste à utiliser un filtre normalisé, c’est-à-dire tel que la
somme de ses coefficients soit égale à 1. On s’assure ainsi que le résultat ne dépassera jamais 255.
* La deuxième consiste à écrêter les valeurs qui dépassent. Il faut alors faire le calcul en utilisant un type qui
supporte des valeurs nettement plus grandes que 255 (et plus petites que 0), et ensuite mettre à 255 toutes les
valeurs supérieures à 255, et à 0 toutes les valeurs inférieures à 0.
* La troisième consiste à faire le calcul comme dans le cas précédent, mais en ramenant ensuite toutes les valeurs
dans l’intervalle [0,255] (avec une relation de proportionnalité par exemple).

Q 134. Essayer les filtres suivants (éventuellement en les appliquant à une image en niveaux de gris) :
 
 −2 −1 0
* Repoussage : F2 = −1
 
1 1

 
 

 
0 1 2
 
0 1 0

* Contours : F3 = 1

−4 1

 
 

 
0 1 0
 
 0 −1 0 
* Réhausseur : F4 = 
 
−1 5 −1
 

 

0 −1 0
 
 
1 2 1
1 
* Moyennage gaussien 3 × 3 : F5 =
. Remarque : on a F5 = 1 · V ∗ t V , où V est la matrice

2 4 2

 
16 
 
 16
1 2 1
 
 1
, ∗ est le produit matriciel, et t V est la transposée de V . En python, si A et B sont des np.ndarray,
 
2

 


  
1
alors np.dot(A, B) renvoie le produit matriciel de A par B (on peut aussi utiliser A@B, ou encore A.dot(B)),
et A.T renvoie la transposée de A.
38. En fait, il faudrait traiter ces éléments de façon spécifique, mais on fait ici l’opération de filtrage de manière simplifiée.
39. Ce n’est pas un produit matriciel.
40. Là aussi, ce n’est pas un produit matriciel.
Info S1 – page 42/ 43
   

1 4 6 4 1 1
 
4 16 24 16 4 4

  
 
1 

  

Moyennage gaussien 5 × 5 : F6 = . Remarque : F6 = 1
·V ∗ t
, où .
   
* 6
 34 36 24 6 256 V V = 
 6
256 
 
 
 

4 16 24 16 4 4

  
 


 
 
 
  
1 4 6 4 1

1

Table des matières


I L’informatique en cpge 1
1 Python au concours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2 Ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
3 Limites du programme sur les éléments de langage Python à connaître . . . . . . . . . . . . . . . . . . . . 1

II Démarrage en douceur : éditeur ou console, affectation, affichage, fonctions, importation de module 2


1 IDE, console, éditeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Premier programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3 Fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4 L’indentation a un sens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
5 Types int et float, typage dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
6 Retourner (ou renvoyer) et afficher sont deux choses différentes . . . . . . . . . . . . . . . . . . . . . . . . 5
7 Chaîne de caractère . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
8 Importation de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
9 Appel de fonction par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
10 Portée lexicale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

III Structures de contrôles 7


1 Boucle for (et listes) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2 Instruction conditionnelle if...elif...else... (début) . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3 Type booléen, opérateurs logiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4 Instruction conditionnelle if...elif...else... (suite) . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5 Boucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
6 Instructions break et continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

IV Retour sur les chaînes de caractères et les listes 11


1 Chaînes de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2 Listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Lancé de dés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4 Observations météorologiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5 Pi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

V Tuples 14

VI Opérations sur les listes, les chaînes et les tuples 15


1 Concaténation (+ et *) : listes, chaînes, tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2 Construction par append successifs : listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3 Construction « en compréhension » : listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4 Méthode pop, fonction del : listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5 Tranche (en anglais slice) : listes, chaînes, tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6 Copie superficielle ou non : listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
7 in et not in : listes, chaînes, tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

VIITracé de courbes (module matplotlib.pyplot) 18


1 Premières figures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2 Première approche pour tracer une courbe : utilisation des listes . . . . . . . . . . . . . . . . . . . . . . . 19
3 Deuxième (et meilleure) approche : utilisation du module numpy . . . . . . . . . . . . . . . . . . . . . . . 19
4 Quelques compléments sur le tracé de courbe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Info S1 – page 43/ 43
VIIIModélisation d’une courbe expérimentale 22
1 Modélisation par une fonction quelconque (avec scipy.optimize.curve_fit) . . . . . . . . . . . . . . . 22
2 Modélisation par une fonction polynomiale (avec numpy.polyfit) . . . . . . . . . . . . . . . . . . . . . . 23

IX Recherche séquentielle dans un tableau unidimensionnel 23


1 Trois algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
a Recherche d’un élément dans une liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
b Recherche du maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
c Recherche du deuxième maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2 Une première approche de la complexité d’un algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
a Évaluation de la complexité temporelle : décompte des opérations élémentaires . . . . . . . . . . . 24
b Ordre de grandeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
c Pire ou meilleur des cas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3 Retour sur les trois algorithme : complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
a Recherche d’un élément dans une liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
b Recherche du maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
c Recherche du deuxième maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

X Dictionnaire 27
1 Le type dict . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2 Recherche dans un dictionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3 Comptage des éléments d’un tableau à l’aide d’un dictionnaire . . . . . . . . . . . . . . . . . . . . . . . . 29

XI Algorithmes avec boucles imbriquées 30


1 Algorithme inconnu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2 Sommes doubles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3 Recherche des plus proches voisins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

XIILecture/écriture de fichiers 30
1 Commandes de base pour la lecture/écriture dans un fichier . . . . . . . . . . . . . . . . . . . . . . . . . 30
2 Répertoire courant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3 Premiers exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4 Loi de Benford (et tracé d’histogramme) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

XIIIAlgorithmes gloutons 33
1 Rendu de monnaie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2 Allocation de ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3 Problème du sac à dos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
a Version fractionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
b Version non-fractionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

XIVManipulation d’images 37
1 Quelques éléments sur le codage numérique d’une image . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2 Premières manipulations d’image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3 Symétrie et rotation « simples » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4 Conversion en niveaux de gris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5 Détection de contour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6 Application d’un filtre quelconque à une image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Vous aimerez peut-être aussi