Sujet
Énoncé
Bienvenue dans l'examen blanc de mi-semestre !
Si vous n'êtes pas en salle machine pendant votre séance 12 de TP... ne lisez pas plus loin ! Le but de ce test est de vous entraîner en conditions réelles, il n'a pas d'intérêt si vous regardez le sujet avant la séance.
Règles du jeu
Le but de cet examen blanc est de faire un point à mi-parcours et de vous permettre de vous positionner par rapport à l'avancement et aux attendus du cours : il n'est pas noté.
Pour que ce test vous apporte quelque-chose, il faut se placer dans des conditions d'examen, c'est-à-dire que :
- vous devez travailler individuellement sans communiquer avec vos camarades ;
- vous avez accès, via le site web du cours, à tous les supports utilisés jusqu'ici (CM, TD, TP), corrections incluses ;
- vous ne devez pas vous servir de vos notes de cours / TD ;
- vous ne devez pas vous servir des données sur votre compte informatique (sources de vos TP précédents, etc.) :
- conservez bien votre « rendu » (même si vous n'avez pas à le rendre !) car on travaillera à nouveau ce sujet lors du prochain TP de BPI.
Crible d'Ératosthène
On va implanter dans cet exercice le fameux crible d'Ératosthène (célèbre informaticien grec du IIIᵉ siècle avant notre ère). Le crible d'Ératosthène est un algorithme permettant de lister tous les nombres premiers inférieurs ou égaux à une valeur maximale donnée.
Le principe de cet algorithme est très simple :
- on demande à l'utilisateur la valeur maximale
val_max
; - on alloue un tableau statique (qu'on appellera
les_premiers
) de booléens numérotés de 1 àval_max
, et on initialise toutes ses cases àTrue
; - on affecte la première case de ce tableau à
False
, car 1 n'est pas un nombre premier ; - on pose
prem
vaut 2 (le premier nombre premier) et on parcourtles_premiers
pour mettre àFalse
toutes les cases dont le numéro est multiple deprem
: 4, 6, 8, etc. ne sont pas des nombres premiers, on les « barre » dans le tableau ; - on passe
prem
au plus petit entier supérieur qui n'a pas été barré et on répète le processus :- le plus petit entier supérieur à 2 et non barré est
prem = 3
: on barre tous les multiples de 3 dans le tableau ; - le plus petit entier supérieur à 3 et non barré est
prem = 5
(4 est un multiple de 2, il a déjà été barré) : on barre tous les multiples de 5 dans le tableau ; - etc.
- le plus petit entier supérieur à 2 et non barré est
- on peut s'arrêter dès que
prem
atteint \sqrt{val\_max}, car on est alors sûr d'avoir déjà barré tous les nombres composés plus grands.
On détaille ci-dessous les différentes étapes à réaliser pour implanter cet algorithme dans un fichier que vous appellerez crible.py
et dont on vous fournit le squelette :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Chaque fonction devra être commentée de façon appropriée, en précisant notamment le rôle et la nature de ses paramètres, ainsi que ce qui est renvoyé à la fin de la fonction le cas échéant. On s'attend à ce qu'une exécution de pylint crible.py
donne un score de 10/10.
On rappelle qu'en Python les tableaux sont forcément indicés à partir de 0 : la case numéro 1 (la première) d'un tableau a l'indice 0, la case numéro 2 (la deuxième) a l'indice 1, etc.
On considérera dans tout l'exercice qu'on travaille sur un tableau statique : vous ne devez utiliser aucune primitive Python manipulant des tableaux dynamiques (append
, del
, index
, etc.), qui vous seraient d'ailleurs parfaitement inutiles vu l'algorithme demandé.
Initialisation du tableau
Commencez par écrire une fonction init_tab(taille)
dont le but est d'allouer un tableau de booléens de la taille voulue, d'initialiser ses cases à True
(sauf la première qui devra être initialisée à False) et de renvoyer ce tableau à la fonction principale. Vous pouvez exceptionnellement utiliser la méthode append
dans cette question si vous ne connaissez pas les compréhensions de listes Python.
Filtrage des multiples d'un nombre donné
Implantez maintenant une fonction filtre(les_premiers, prem)
dont le rôle est d'affecter à False
toutes les cases du tableau les_premiers
dont le numéro est un multiple de prem
.
Affichage du résultat
Écrivez ensuite une fonction affiche(les_premiers, val_max)
qui affiche les numéros des cases contenant True
, sous le format suivant (en supposant que val_max
vaut 10) : Les nombres premiers inférieurs ou égaux à 10 sont : 2 3 5 7
.
Programme principal
Implantez enfin la fonction main
qui sera le point d'entrée du programme et devra donc demander à l'utilisateur la valeur maximale, puis utiliser les fonctions précédentes pour implanter l'algorithme du crible d'Ératosthène.
Votre programme devra bien vérifier que la valeur entrée par l'utilisateur est un entier supérieur ou égal à 2. Dans le cas contraire, on arrêtera immédiatement le programme avec un message d'erreur adapté.
Pour cela, vous lancerez une exception de type ValueError
.
Ce lien vous emmènera vers la documentation du module math
qui contient notamment les fonctions floor
(partie entière) et sqrt
(racine carrée) qui vous serons vraisemblablement utiles.
Correction
Cliquez ici pour révéler la correction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Compression / décompression d'images en noir et blanc
On va travailler dans cet exercice avec des images au format Portable BitMap file format (PBM) qui ressemble beaucoup au format PGM qu'on a vu en TP, la différence étant que PBM concerne des images monochromes en noir et blanc, alors que PGM permet de gérer des images en niveaux de gris.
L'avantage de ces formats (et de leur grand frère PPM pour les images en couleurs sur 16 bits) est que l'image est stockée dans un fichier texte très facile à interpréter et donc facilement portable d'un système à un autre. L'inconvénient majeur est que stocker des pixels sous la forme de caractères n'est vraiment pas optimal en termes d'espace disque et mémoire et engendre des fichiers de très grandes tailles même pour des images modestes.
On va donc implanter un algorithme de compression des images basé sur un codage par longueur de plage (Run-Length Encoding (RLE) en anglais) dont le principe est tout simplement de factoriser les suites de pixels égaux en comptant leurs nombres d'occurrences consécutives. Par exemple, si un fichier texte contient la suite de caractères aaaaaaaabbcccccaaa
, on peut le compresser simplement en 8a2b4c3a
. Évidemment, ce type de codage est d'autant plus efficace que le fichier contient de grandes plages de valeurs égales, ce qui sera vraisemblablement souvent le cas pour des images en noir et blanc.
Le format PBM
On donne un exemple d'une image PBM (téléchargeable en cliquant sur ce lien) que vous pouvez ouvrir en tapant eog image.pbm
dans un terminal ou en double-cliquant dessus dans le gestionnaire de fichier. Vous noterez au passage le style très seventies de cette image, qui ne nuit cependant pas du tout à la puissance de son message !
On fournit aussi une petite image représentant un E
majuscule (téléchargeable en cliquant sur ce lien) pour vous permettre de comprendre le format, et aussi de tester votre programme sur un petit exemple lisible.
Si vous ouvrez l'image dans un éditeur de texte (par exemple code e_maj.pbm
dans le terminal), vous comprendrez rapidement le format texte basique utilisé :
- la première ligne contient la chaîne "P1" qui indique qu'il s'agit d'une image PBM (magic number) ;
- la deuxième ligne contient la largeur puis la hauteur de l'image, en nombre de points, sous la forme de deux entiers séparés par un ou plusieurs caractères d'espacement (espace ou tabulation) ;
- le reste du fichier contient les couleurs des points de l'image (0 représente du blanc et 1 représente du noir), potentiellement séparés par (zéro ou) un ou plusieurs caractères d'espacement (espace ou tabulation) ou retours à la ligne : tous les séparateurs sont facultatifs et ignorés.
On vous donne également une version simplifiée de l'image du E
majuscule (téléchargeable en cliquant sur ce lien) qui ne contient aucun séparateur superflu, afin de vous permettre d'écrire et tester une version simplifiée de votre programme si vous le souhaitez.
Lorsque les images sont très larges, il est recommandé d'aller à la ligne au bout de 70 caractères, ce qui ne change rien au contenu de l'image : c'est le cas pour le fichier image.pbm
.
Le format compressé
Une image PBM compressée sera également un fichier texte, sous le format suivant :
- la première ligne contiendra la largeur puis la hauteur de l'image, en nombre de points, sous la forme de deux entiers séparés par un espace ;
- chacune des lignes suivantes contiendra un entier naturel correspondant au nombre de répétitions de la couleur courante, en supposant qu'on démarre avec du blanc.
Par exemple, le fichier :
1 2 3 4 5 6 |
|
Et le fichier :
1 2 3 4 5 6 7 |
|
Travail demandé
Vous devez écrire un programme rleimg.py
prenant un fichier en paramètre sur la ligne de commande et capable de compresser ou décompresser l'image PBM qu'il contient. On s'attend à ce que le programme puisse être appelé selon le schéma d'usage suivant : ./rleimg.py (-c ou -d) fichier_source
où :
- le premier paramètre dira si on doit compresser (
-c
) ou décompresser (-d
) le fichier ; - le deuxième paramètre sera tout simplement le nom du fichier source.
On ne détaille pas précisément comment vous devez implanter votre programme, mais on s'attend à ce que vous respectiez les directives ci-dessous.
Vous ne devez lire qu'une seule fois le fichier source et compter à la volée les nombres de répétitions (pour la compression) ou générer à la volée les points (pour la décompression). Vous utiliserez un tableau dynamique comme mémoire tampon pour la compression afin de permettre la vérification des nombres de répétitions (comme demandé ci-dessous), mais pas pour la décompression qui peut se faire en une seule passe sans stockage intermédiaire des données.
On s'attend à ce que vous implantiez des vérifications d'intégrité raisonnables dans votre code, et notamment :
- vous devez vérifier que les paramètres passés sur la ligne de commande respectent bien le schéma d'usage détaillé plus haut (c.-à-d. vérifier que le premier paramètre est bien
-c
ou-d
) et afficher un message approprié puis terminer proprement le programme si ce n'est pas le cas ; - pour la compression, vous devez vérifier (et là encore terminer le programme avec un message d'erreur sinon) :
- que la première ligne du fichier source contient bien la chaîne
"P1"
; - que le fichier source contient bien largeur \times hauteur points ;
- que la somme des nombres de répétitions calculée à l'issue de la compression est bien égale au nombre de points dans le fichier source (cette vérification servira en fait à détecter des erreurs dans votre code) ;
- que la première ligne du fichier source contient bien la chaîne
- pour la décompression, vous pouvez partir du principe que le fichier en entrée a été produit correctement pour essayer de générer le résultat le plus efficacement possible.
N'hésitez pas à utiliser des exceptions pour gérer proprement les cas d'erreur.
Votre programme devra bien évidemment être découpé en sous-fonctions, que l'on vous laisse libre de définir comme cela vous parait le plus pertinent pour factoriser autant de possible le code. On attend bien sûr que tout soit commenté de façon appropriée, avec notamment la nature et le rôle des paramètres de vos fonctions, et ce qu'elles renvoient.
Le résultat de l'exécution du programme sur un fichier source sera envoyé directement sur la sortie standard (stdout) à l'aide de la fonction print
: vous pourrez générer un fichier en redirigeant la sortie comme on vous l'a appris en Unix, par exemple en exécutant ./rleimg.py -c image.pbm > resultat.rle
.
Si vous voulez comparer directement le résultat d'une compression + décompression avec le fichier image.pbm
, il vous faudra fractionner les lignes générées tous les 70 caractères, ce que vous pouvez soit implanter directement dans votre code, soit réaliser a-posteriori en utilisant la commande Unix fold
qui fait exactement ça : ./rleimg.py -d resultat.rle | fold -w 70 > resultat.pbm
. Vous pourrez alors utiliser la commande diff image.pbm resultat.pbm
classique pour vérifier que les deux fichiers sont égaux.
On rappelle l'existence de la méthode split
(dont la documentation est accessible en cliquant sur ce lien) qui devrait vous faciliter la vie pour analyser le fichier source. La documentation du module sys
ainsi que celle concernant les entrées-sorties en Python vous seront vraisemblablement utiles.
Correction
Cliquez ici pour révéler la correction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
|