Académique Documents
Professionnel Documents
Culture Documents
XIILecture/écriture de fichiers 5
1 Commandes de base pour la lecture/écriture dans un fichier . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Répertoire courant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3 Premiers exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4 Loi de Benford (et tracé d’histogramme) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
XIIIAlgorithmes gloutons 8
1 Rendu de monnaie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2 Allocation de ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3 Problème du sac à dos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
a Version fractionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
b Version non-fractionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
XIVManipulation d’images 12
1 Quelques éléments sur le codage numérique d’une image . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2 Premières manipulations d’image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Symétrie et rotation « simples » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4 Conversion en niveaux de gris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5 Détection de contour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
6 Application d’un filtre quelconque à une image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
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) 1 . 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.
1. En fait, une clé doit être un objet non muable, de manière à pouvoir être hachable (voir en deuxième année). En particulier, une clé ne
peut pas être une liste ou un dictionnaire
Info S1 (suite) – page 2/ 16
* 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 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 2 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 3 .)
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.
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.
2. C’est en Bosnie.
3. Spoiler : 96 027 habitants
Info S1 (suite) – page 4/ 16
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).
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
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).
Info S1 (suite) – page 5/ 16
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é?
• 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:/mondossierdetppython/monsousdossier/fichier.txt'). Ou bien on peut utiliser un chemin relatif
(= un chemin donné « à partir » du répertoire courant) : open('monsousdossier/fichier.txt').
Remarque : il faut utiliser des slashs / plutôt que des antislashs \ 6 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.
6. Car cela produit du code (un peu) plus portable d’un système à l’autre.
Info S1 (suite) – page 7/ 16
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 : envirion 178) ;
• la moyenne des scores des 100 premières joueuses (indication : envirion 1420) ;
• la moyenne des scores des joueuses françaises (indication : envirion 169) ;
• la moyenne des rangs des joueuses dont le prénom commence par un N (indication : envirion 761).
Indication : utiliser la méthode .split(), avec en argument le bon caractère.
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.
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.
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 abcisse 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’occurences 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 7 ). Ne
pas oublier le plt.show() après.
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 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 : en fait, dans la vraie vie, le commerçant ne fait pas comme ça, mais exactement dans l’ordre inverse 9 .
Autre remarque : en fait, 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).
• 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 :
8. Le problème peut être avec ou sans contrainte.
9. 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 (suite) – page 9/ 16
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).
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,21 €.
Q 112. Essayer aussi avec 77,22 €. Quel est le problème (ne pas hésiter à mettre un print ou deux 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ée 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] 10 .
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.
10. 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 (suite) – page 10/ 16
• 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.
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.
Info S1 (suite) – page 11/ 16
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]].
• 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]] ;
Info S1 (suite) – page 12/ 16
• 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.
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.
11. Remarque : dans ce cas, on peut zoomer autant que souhaité, l’image ne sera jamais pixelisée, car l’afficheur recalcule les pixels à afficher
à chaque changement de niveau de zoom.
12. 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 (suite) – page 13/ 16
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) :
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).
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 ′ ′
p(i ,j ) − p(i,j)
d(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
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).
Info S1 (suite) – page 15/ 16
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.
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 13 .
* 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 14 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 15 . 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).
13. 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.
14. Ce n’est pas un produit matriciel.
15. Là aussi, ce n’est pas un produit matriciel.
Info S1 (suite) – page 16/ 16
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.
1 4 6 4 1
1
4 16 24 16 4 4
1
* Moyennage gaussien 5 × 5 : F6 = . Remarque : , où .
1 t
·V ∗
6 34 36 24 6
F 6 = 256 V V =
6
256
4 16 24 16 4 4
1 4 6 4 1
1