Nous continuons les manipulations de listes, et apprenons à utiliser des modules.
Le sujet porte sur un algorithme de compression sans perte, utilisé notamment pour les encoder les images au format GIF. Il porte le nom de ses trois inventeurs. vous trouverez toutes les informations inutiles ici.
Ce TP est un peu plus conséquent que les précédents, nous allons donc créer un projet en plusieurs fichiers. commencez par créer un répertoire dédié à vos programmes Caml, si vous n'en avez pas encore. Créez un fichier texte de nom _tags, contenant uniquement le texte suivant:
puis créez un sous-répertoire Lzw, dans lequel vous mettrez les fichiers Ocaml pour ce TP.
Ensuite, récupérez les fichiers suivants, que vous placerez dans Lzw :
Les modules Trie et Stream_lzw vous sont fournis gratuitement, vous n'avez pas à modifier ces fichiers. L'interface de Binary est fournie mais vous en écrirez l'implémentation. Vous aurez aussi à écrire l'implémentation et l'interface du module principal Lzw.
Le module Binary défini le type des bits:
et les opérations de conversion entre int, listes de bits, et code ascii. Un code ascii est un entier entre 0 et 255 . Les encodages d'entiers par des listes de bits seront de type little-endian, c'est-à-dire avec bit de poids faible en tête.
Implémenter int_of_bit_list convertissant une liste de bits en entiers. Puis son inverse bit_list_of_int convertissant un entier en liste de bits. Remarquez que la deuxième fonction prend en argument la longueur de l'encodage : la liste retournée par bit_list_of_int doit impérativement avoir cette longueur.
Le module List propose une fonction List.flatten très pratique, qui transforme une liste de listes en liste simple. Nous avons besoin d'une fonction inverse, qui prenant une liste, regroupe les éléments par blocs de
cut transvase donc un certain nombre d'éléments de la deuxième vers la première liste.
En déduire une fonction unflatten, dont le comportement est:
Implémentez maintenant les fonctions suivantes :
Pour ascii_of_bit_list, vous commencez par ajouter 7 bits O en fin de la liste à convertir, puis utilisez unflatten et List.map.
À l'issue de cette question, vous devriez pouvoir compiler votre fichier en un fichier objet d'extension .cmo. Pour cela, placez-vous dans le répertoire où se trouve le fichier tags, et lancer la commande:
S'il ne répond pas qu'il y a une erreur, un fichier _build/Lzw/binary.cmo devrait être apparu.
Profitez-en pour compiler les deux autres modules, en lançant :
Créez maintenant un fichier lzw.ml dans le sous-répertoire Lzw. Pour pouvoir utiliser les autres modules sous le toplevel, entrez et exécutez les directives suivantes:
Remark 1: Il faudra impérativement enlever les directives (les lignes commençant par #) lorsque vous voudrez compiler le programme.
Le principe de compression LZW est le suivant : en tout temps on garde un dictionnaire de listes de bits, contenant au moins [O] et [I]. À chaque liste du dictionnaire est associé un entier unique, qui est en fait le code compressé de cette liste de bits.
Une itération de l'algorithme consiste à lire la plus longue séquence de bits (préfixe) dans le fichier à compresser, telle que cette liste apparaisse dans le dictionnaire. Notons cette liste
Prenons un exemple, notre dictionnaire est :
Listes | Codes | Listes | Codes |
---|---|---|---|
[O] | 0 | [I;O;I] | 4 |
[I] | 1 | [I;I;I] | 5 |
[I;I] | 2 | [O;O] | 6 |
[I;O] | 3 | [I;O;I;I] | 7 |
et notre fichier commence par [I;O;I;O;O;I;I;I;O;O;...]. Le plus long préfixe du fichier apparaissant dans le dictionnaire est [I;O;I], de code
Le module Trie fournit une structure de dictionnaire de listes. Prenez connaissance de son interface. C'est un module paramêtré par un type ordonné (tout comme Map et Set dans la librairie standard). Définissez un module pour les dictionnaires de listes de bits.
Créez un type compr_context qui contiendra le dictionnaire, et le plus petit code non-encore attribué dans le dictionnaire. Créez une valeur initiale pour ce type, telle que le dictionnaire contienne les listes de longueur 1 . Puis ajouter une fonction qui ajoute une nouvelle liste dans le dictionnaire.
Implémenter l'algorithme de compression LZW, de type :
compression est une fonction récursive. Ses arguments sont :
Il faut maintenant convertir la liste d'entiers en liste de bits. La méthode que nous utilisons est peu efficace, mais facile à implémenter :
En suivant cette méthode, écrire une fonction générale :
qui lit un fichier sur l'entrée standard, le compresse, et écrit son code en sortie standard.
Il nous faut maintenant donner un algorithme pour retrouver le fichier original depuis le fichier compressé. Le principe est étrangement similaire à l'algorithme de compression. Cette fois-ci, on nous donne une liste d'entiers et on doit retrouver une liste de bits.
Nous allons encore utiliser un dictionnaire, mais cette fois il associe à chaque code une liste de bits. Initialement, le dictionnaire ne connait que les codes
Étudions un exemple. Supposons que le dictionnaire soit le suivant :
Code | Liste | Code | Liste |
---|---|---|---|
0 | [O] | 5 | [O;O;I;I] |
1 | [I] | 6 | [I;O;I] |
2 | [O;O] | 7 | [I;I] |
3 | [O;O;I] | 8 | [I;O;I;O] |
4 | [I;O] | 9 | [O;O;O] |
et les prochains entiers à décompresser sont [6;8;11;3;5;...]. La liste associée à
On lit ensuite l'entier suivant, c'est
Puis on recommence avec
Instanciez une structure de dictionnaire d'entiers, en utilisant le module Map de la librairie standard.
Comme à la question 4, créez un type dec_context, contenant le dictionnaire des codes et la valeur du plus petit code non-attribué. Créez une valeur initiale définissant les codes
Codez l'algorithme de décompression :
Ses arguments sont ;
Il reste à lire l'entrée standard et la convertir en liste d'entiers, en suivant le protocole inverse de la question 7.
Terminer le travail en ajoutant la fonction suivante :
Compiler ensuite votre programme en vous plaçant dans le répertoire contenant le fichier _tags (n'oubliez pas de mettre en commentaire les directives du toplevel au début du fichier) :
Si votre programme est correct, vous obtenez un exécutable _build/Lzw/lzw.native ou ./lzw.native. Testez votre programme, par exemple :
Si cela fonctionne correctement, télécharger ce texte, et tentez de le compresser. Que se passe-t-il, et pourquoi ?