From 3200e5ac2fa06fbac73ede8b96061b050574bf89 Mon Sep 17 00:00:00 2001 From: docusland Date: Mon, 16 Oct 2023 08:52:32 +0200 Subject: [PATCH] Exercise definition --- README.md | 92 +++++++++++++++------------------------- files/quadtree.png | Bin 0 -> 8908 bytes files/quadtree.txt | 6 +++ files/quadtree_easy.txt | 1 + tests/test_quadtree.py | 48 +++++++++++++++++++++ 5 files changed, 89 insertions(+), 58 deletions(-) create mode 100644 files/quadtree.png create mode 100644 files/quadtree.txt create mode 100644 files/quadtree_easy.txt create mode 100644 tests/test_quadtree.py diff --git a/README.md b/README.md index 4e3433b..c67a138 100644 --- a/README.md +++ b/README.md @@ -3,69 +3,45 @@ Vous trouverez ici un ensemble d'exercices permettant de pratiquer python mais également découvrir quelques pans annexes de l'informatique en général. Ces exercices sont issus d'un livre réalisé par Pascal Lafourcade et Malika More. -## Exercice : A ski -Décryptez le message stocké dans le fichier `files/a_ski.txt`. +## Exercice : Arbre quaternaire +Un quadtree ou arbre quaternaire (arbre Q) est une structure de données de type arbre dans laquelle chaque nœud a quatre fils. Les quadtrees sont le plus souvent utilisés pour partitionner un espace bidimensionnel en le subdivisant récursivement en quatre nœuds. +![img.png](files/quadtree.png) -Après avoir compris la technique de codage utilisé, saurez-vous implémenter un programme en python permettant de le déchiffrer? +Il existe plusieurs types de quadtree. Dans notre cas il s'agit d'un quadtree "region". +Le quadtree «région» représente une partition de l'espace en deux dimensions en décomposant la région en quatre quadrants égaux, puis chaque quadrant en quatre sous-quadrants, et ainsi de suite, avec chaque «nœud terminal» comprenant des données correspondant à une sous-région spécifique. Chaque nœud de l'arbre a exactement : soit quatre enfants, soit aucun (cas d'un nœud terminal). +Chaque `Noeud` comportant quatre éléments. Il s'agit d'une technique connue pour l'encodage d'images. Pour simplifier, les images sont carrées, de couleur noir et blanc +et de côté 2^n. -> Ici vous allez découvrir la manipulation de fichiers, la conversion de str en int et quelques méthodes natives du langage python. - -## Exercice cryptarithme -Chaque lettre de l'opération représente un chiffre différent entre 0 et 9. Ecrivez un programme permettant de déterminer la valeur de chaque lettre pour que l'opération soit correcte, sachant que le premier chiffre de chaque nombre ne peut être un `0`. -> SEND + MORE = MONEY - -> HUIT + HUIT = SEIZE - -> UN + UN + ONZE = TREIZE - -> CINQ + CINQ + VINGT = TRENTE - -Saurez-vous implémenter un script python permettant de trouver les chiffres associés aux lettres? - -Vous avez le fichier `files/cryptarithme.txt` à votre disposition si vous le souhaitez. - -> Cet exercice implique l'utilisation de boucles imbriquées. - -## Exercice: Compter comme un shadok -> Pour compte les matins, les Shadoks ont un système de numérotation basé uniquement sur les syllabes suivantes : ->GA, BU, ZO et MEU. ->Voici par exemple un nombre shadok : BU MEU ZO MEU GA GA - -Saurez-vous programmer un convertisseur de nombre shadoks en nombres décimaux pour découvrir quel est le nombre caché derrière le fichier `files/shadoks.txt` et ainsi découvrir la date de diffusion du premier épisode de cette série culte? - -> Cet exercice implique la compréhension des bases en mathématiques. - -## Exercice : Stéganographie -Le principe de stéganographie est d'utiliser une technique pour passer un message secret d'une manière dissimulée tout en restant lisible pour celui qui sait comment trouver. -Au sein du fichier `stegano.png` se cache un texte secret. Chaque pixel a été très légèremet modifié. -Il faut extraire le bit de poids faible de chaque couleur de chaque pixel. -Puis d'en refaire des octets de 8 bits. - -Exemple: +Un noeud à quatre fils est représenté : ```python -pixel = image[0][0] -r = red(pixel) % 2 -g = green(pixel) % 2 -b = blue(pixel) % 2 -a = alpha(pixel) % 2 +from __future__ import annotations -pixel1 = image[1][0] -r1 = red(pixel1) % 2 -g1 = green(pixel1) % 2 -b1 = blue(pixel1) % 2 -a1 = alpha(pixel1) % 2 +class QuadTree: + def __init(hg: bool|QuadTree, hd: bool|QuadTree, bg: bool|QuadTree, bd: bool|QuadTree): + pass + @property + def depth(self) -> int: + """ Recursion depth of the quadtree""" + return 1 + @staticmethod + def fromFile(filename): + # Your code here + pass + @staticmethod + def fromList(data): + pass + + def paint(self): + """ Textual representation of the QuadTree""" -first_letter = chr(bin2dec(''.join((r,g,b,a,r1,g1,b1,a1)))) -``` -Vous aurez très probablement besoin d'utiliser PIL. -```shell -pip install pillow +class TkQuadTree(QuadTree): + def paint(self): + """ TK representation of a Quadtree""" ``` -## Exercice : Canaux cachés -La fonction fournie dans le fichier `files/canaux_caches.py` permets de vérifier si -un code saisi est contenu dans la variable essai +A partir du fichier `files/quadtree.txt`, générez le QuadTree associé. +Puis, réalisez une interface graphique en utilisant la classe `TkQuadTree`, permettant de la représenter. -Saurez-vous écrire un programme qui permets à votre ordinateur à coup sûr en moins de 3 minutes de découvrir le code PIN saisi par l'utilisateur ? -L'ordinateur ne doit pas utiliser la variable pin mais uniquement la méthode checkpin. -La fonction checkpin fournie ne dois pas être modifiée. \ No newline at end of file +Bonus : +Remplacez les valeurs binaires des feuilles par des valeurs numériques, combinez celà à un [tileset](https://docs.godotengine.org/en/stable/_images/using_tilesets_kenney_abstract_platformer_tile_sheet.webp). +Et voilà, vous avez généré votre tilemap par le biais d'un quadtree. \ No newline at end of file diff --git a/files/quadtree.png b/files/quadtree.png new file mode 100644 index 0000000000000000000000000000000000000000..6a12f5687fb142e8cd006db6749ab1b2690219cc GIT binary patch literal 8908 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 int: + """ Recursion depth of the quadtree""" + return 1 + + @staticmethod + def fromFile(filename: str) -> QuadTree: + """ Open a given file, containing a textual representation of a list""" + data = [] + with open(filename) as f: + lines = f.read() + data = eval(lines) + print(data) + return QuadTree.fromList(data) + + @staticmethod + def fromList(data: list) -> QuadTree: + """ Generates a Quadtree from a list representation""" + if isinstance(data, int): + return data + if len(data) == QuadTree.NB_NODES: + q = QuadTree( + QuadTree.fromList(data[0]), + QuadTree.fromList(data[1]), + QuadTree.fromList(data[2]), + QuadTree.fromList(data[3]) + ) + return q + +def test_sample(): + filename = "../files/quadtree.txt" + q = QuadTree.fromFile(filename) + assert q.depth == 4 + +def test_single(): + filename = "../files/quadtree_easy.txt" + q = QuadTree.fromFile(filename) + assert q.depth == 1 \ No newline at end of file