Projet Caml : Codage de Huffman Didier Siphaxay 1A INFO N7 : Groupe F 11 décembre 2007 Résumé Ce projet est basé sur l’algorithme de compression / décompression crée par M. Huffman en 1952 .La compression de données fait partie du quotidien de millions d’utilisateurs d’ordinateurs à travers le monde. Avec la multiplication des sources d’informations il est préférable de mieux gérer l’espace mémoire qui est souvent assez limité sur nos machines même si de nos jours les capacités sont de plus en plus grandes pour une taille de plus en plus petite. 1 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Table des matières 1 Présentation générale 1.1 Introduction . . . . . . . 1.2 Distribution fournie . . 1.3 Les spécifications exigées 1.4 Choix de spécifications . . . . . . . sujet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 4 5 2 Conception 2.1 Structure globale de l’application 2.2 Burrows Wheeler . . . . . . . . . 2.3 Move to Front . . . . . . . . . . 2.4 Huffman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 7 8 9 3 Codage 3.1 Burrows Wheeler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Move to Front . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Huffman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 12 13 4 Tests 4.1 Méthodes de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 15 5 Conclusion 5.1 Travail effectué . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Le langage Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Avis personel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 16 16 16 6 Annexes 6.1 Listing des fichiers de test (globaux) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Statistiques sur les tests usines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 17 . . . . du . . 2 Siphaxay Didier Programmation fonctionnelle CAML 1 1.1 Projet P1 Présentation générale Introduction L’objectif de ce projet est la réalisation d’un algorithme de compression / décompression basé sur les arbres de Huffman. Pour améliorer l’efficacité de cette technique, deux autres méthodes lui seront adjointes : la méthode de Burrows-wheeler ainsi que celle du ”Move-to-Front”. Pour compresser des données, on utilise une compression par arbre (méthode d’Huffman). Celle ci peut se décomposer ainsi : on va d’abord séparer la donnée source en unités de même taille (e.g. des caractères) et associer chacune de ces unités à sa multiplicité dans dans la donnée source. Cette méthode est ainsi semi-adaptative, en effet on analyse une première fois le document afin d’extraire des statistiques sur les unités que l’on range dans l’arbre d’huffman pour pouvoir mieux optimiser la compression. Il existe bien d’autres méthodes qui sont basés soit sur la fréquence d’apparatition de l’unité dans une langue soit d’autres paramètres. Pour retrouver l’ordonnancement de ces unités, on va se servir d’une suite de bits qui nous permettra de naviguer dans l’arbre ainsi composé. Le nombre de bit attribué à une unité dépendra de sa multiplicité dans la donnée source, ainsi les unités les plus fréquentes seront codés sur un code binaire très court alors que les données relativement peu présentes le seront par des codes plus long. Pour améliorer l’efficacité de cette méthode. on va adjoindre une méthode de codage différentiel qui va évaluer les distances entre les unités et leurs précédentes dans la données sources. Cette méthode ne compresse pas les données mais permet d’obtenir une compression plus élévés grâce au codage de Huffman dans le cas où les occurences d’une même unité sont proches. En effet dans ce cas les distantes sont relativement petites et on obtient donc une sensible augmentation de donnée redondantes (gage de bonne compression pour la compression par arbre). C’est l’objet de la méthode ”Move-to-front”. Enfin pour favoriser la méthode précedente, la méthode de ”Burrows-wheeler” entre en jeu. Cette méthode n’est rien d’autre qu’une méthode permettant de rapprocher les unités identiques entre elles afin de réduire leurs distances relatives dans la donnée source. Elle n’engendre elle non plus aucun gain de compression mais elle est facilement reversible. Cette méthode est cependant assez lourde et ne peut être appliqué à des données trop grandes. 1.2 Distribution fournie Voici le listing des fournitures de l’archive qui a été mise à notre disposition pour ce projet. – main.cmo – defaut.mli – defaut.cmi – defaut.cmo – huffman.mli – movetofront.mli – burrows wheeler.mli – Makefile – ... Les différents fichiers ”mli” sont des fichiers de spécification que l’on respecte au travers des types à adopter pour le codage. Ces dernières assurent l’interface entre le programme principal et les différents modules mis en place. L’item ”main.cmo” est le fichier objet issue de la compilation du projet. Cet objet est donc la pour assurer la dernière étape de la création de l’éxécutable ”huffman” par le procédé d’édition des liens grâce à la commande ”make”. Les phases d’entrées-sorties ainsi que l’interaction avec l’utilisateur qui permet d’appeler les différents modules en mode encodage ou décodage sont implantées dans ce fichier. Les items ”defaut.cmo”, ”defaut.cmi”, ”defaut.mli” sont des fichiers qui permettent de remplacer les fonctions que l’on doit coder dans le cas d’un malfonctionnement de ces derniers. Ces fichiers ne permettent aucune compression des données et servent donc seulement de support pour la compilation. 3 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Enfin le fichier ”Makefile” est un fichier contenant des commandes shell qui permettent de compiler et faire l’édition des liens de l’ensemble du programme. On peut aisément les programmer pour n’importe quel type de langage. Leur utilisation passe par la commande ”make”. Les bases étant posées on doit se questionner sur les contraintes du projet : on en a déjà un exemple avec les différents fichiers mis à notre disposition. 1.3 Les spécifications exigées du sujet Le projet doit être écrit en Caml, langage de programmation imposé, et ceci dans un style programmation fonctionnelle i.e. sans tableaux, ni réferences, ni procédures. Pour ce projet, il nous a été fournie une distribution qui restreint déjà notre travail. En effet les fichiers ”mli” cités précédemment sont là pour poser un contrat entre le programmeur et le spécificateur en énonçant les profils des fonctions attendues. Voici toutes les fonctionnalités développées dans le cadre de ce projet que l’on peut retrouver dans les fichiers ’mli’. – Burrows wheeler : encode Encode en utilisant la transformée de Burrows-Wheeler. Renvoie l’indice de position ainsi que la séquence des derniers caractères – Burrows wheeler : decode Décode la séquence à partir de l’indice de position et la séquence des derniers caractères. – Move to front : encode Encode une liste d’unités grâce à une fonction de codage arbitraire par défaut. Pour les caractères, cela peut être le code ASCII par exemple – Move to front : decode Décode la liste d’unités grâce à une fonction de décodage par défaut. Cette fonction doit être la réciproque de celle utilisée par la fonction ’encode’ – Huffman : type ’a huffman (à definir) Type de l’arbre binaire utilisé pour coder le document – Huffman : build-tree Prend une liste d’éléments et construit l’arbre de codage – Huffman : encode Prend une liste d’éléments et renvoie le couple (arbre de codage de huffman, codage binaire de la liste) – Huffman : decode Prend un arbre de codage de huffman et une liste de booléens et reconstruit la liste d’éléments décodée Un jeu de test sera élaboré pour un ”test site” permettant de vérifier la fonctionnalité du programme présenté. Ce jeu de test devra être le plus fin possible rassemblant l’intégralité des cas possibles ou du moins dans les limites posées par le programme principal codé dans le fichier ”main.cmo”. 4 Siphaxay Didier Programmation fonctionnelle CAML 1.4 Projet P1 Choix de spécifications Contraintes personnelles : même si le client ne l’a expressement pas éxiger, pour un souci de rapidité, je préfererai concevoir les différentes fonctionnalités dans un vision d’optimisation temporelle puisque c’est une priorité qui sera imposé dans le travail d’ingénieur. Le deuxième effet étant de ne pas faire patienter l’utilisateur du programme trop longtemps avant d’avoir un résultat. Le choix majeur dans ce projet fut l’élaboration d’un type ’a huffman de façon à optimiser la taille de la donnée compressé. Pour cela on a du passer d’abord par un type qui permettait d’équilibrer l’arbre de codage pour permettre d’obtenir des codes binaires d’une longueur de ’log n’ pour un élément d’une liste de longueur n. Listing 1: Type ’a arbre : type intermédiaire type ’a arbre = | Feuille of int ∗’a |Noeud of (’a arbre∗int∗’a arbre );; Listing 2: Type ’a huffman type ’a huffman = |Vide |Leaf of ’ a |Node of ’a huffman∗’a huffman;; La liste booléene associé à cet arbre est un peu à part mais fait partie entière de la donnée compressée. Les fonctionnalitées auxquelles aura accès l’utilisateur sont au nombre de, en tout et pour tout, 2 ; celle pour compresser les données et celle pour décompresser des fichiers. Ceci à travers les commandes suivantes : – huffman -e <<fichier>> : produit le fichier codé <<fichier>>.encoded à partir du fichier source <<fichier>> – huffman -d <<fichier>> : produit le fichier décodé <<fichier>>.decoded à partir du fichier codé <<fichier>>.encoded 5 Siphaxay Didier Programmation fonctionnelle CAML 2 2.1 Projet P1 Conception Structure globale de l’application La conception de cette application réside dans l’agencement des 3 méthodes servant à la compression de Huffman. ainsi : Fig. 1 – Schéma général Dans la présentation générale, on a fait mention de l’ajout de 2 méthodes qui s’imbrique dans le processus : ici on le voit clairement. La transformée de Burrows-Wheeler va fournir une liste dont les éléments identiques sont rapprochés entre eux. Puis le module ”Move-to-Front” prend la relève en codant les distances relatives pour que le codage de Huffman soit efficace. Les modules sont donc répatis de la manière suivante : – Burrows wheeler – Move to front – Huffman Chacun de ces modules contiennt de paire une fonction d’encodage et de décodage dont les algorithmes sont énoncés dans la section ’Codage’. Ici l’intégration des modules dans l’application principale n’est pas de notre ressort puisque déjà fournie par le biais de l’archive. Ici nous allons donc simplement énoncer les types retenus qui réaliseront le profil des fonctions à coder ainsi qu’un descriptif des différentes méthodes. 6 Siphaxay Didier Programmation fonctionnelle CAML 2.2 Projet P1 Burrows Wheeler Fonction encode : – définition : | Fonction : encode : ’a list -> (int*’a list) : | Resultat : Renvoie l’indice de position ainsi que la séquence des derniers caractères. Usage : On va suivre un exemple d’encodage pour déterminer l’algorithme à retenir afin de le coder. Dans un premier temps on crée la liste des permutations circulaires de la liste à coder : s1 T E T X E E T E T X X E T E T T X E T E s5 E T X E T Après un tri, on observe dans la colone s5 un rapprochement des caractères, ceci est le fondement de cette méthode. On va donc récupérer cette dernière colonne et repérer la ligne où apparait la liste à coder. position 1 2 3 4 5 s1 E E T T X T X E E T E T T X E X E E T T s5 T T X E E Ainsi le résultat va être (indice de la position : 4, et liste codée [’T’;’T’;’X’;’E’;’E’]) Fonction decode : – définition : | Fonction : decode : (int*’a list) -> ’a list: | Resultat : Décode la séquence à partir de l’indice de position et la séquence des derniers caractères. Usage : A partir de la liste codée (et celle qui est classée) et la première position. On obtient ce tableau suivant : 1 2 3 4 5 Codé T T X E E Classé E E T T X Pour reconstituer ce mot il faut partir dans la liste classée à la 4ème position c.a.d. le 2ème T puis on cherche le 2ème T dans la liste codée pour trouver ensuite le 2ème E et on continue en cherchant de 2ème E dans la liste codée et ainsi de suite . . . On obtient donc par reconstitution : le mot ”TEXTE” 7 Siphaxay Didier Programmation fonctionnelle CAML 2.3 Projet P1 Move to Front Fonction encode : – définition : | Fonction : encode : (’a -> int) -> ’a list -> ’a : | Resultat : Encode une liste d’unités grâce à une fonction de codage arbitraire par défaut. Pour les caractères, cela peut être le code ASCII par exemple Usage : Pour simplifier, on utilise des caracteres en ne considérant que les lettres de l’alphabet mais cette démarche s’applique sur tout type d’éléments à coder grâce à une fonction qui permet de coder les éléments en entiers (e.g. Char.code). Le tableau est tout d’abord initialisé en rangeant les caractères utilisés pour le codage comme ceci : Indice 0 1 2 3 4 5 6 . . . 25 Caractère A B C D E F G . . . Z Lorsqu’un caractère est lu, son indice est émis, puis ce caractère est placé en première position et tous les autres caractères décalés (d’où le nom de Move to Front). Par exemple si le premier caractère à coder est un E, le tableau deviendrait : Indice 0 1 2 3 4 5 6 . . . 25 Caractère E A B C D E F . . . Z Ainsi, lorsque des caractères semblables se suivent (cas de la transformée de Burrows-Wheeler), le flux émis contiendra beaucoup de 0, ce qui dans une compression statistique (type codage de Huffman) augmentera considérablement le gain de compression. On note que dans ce cas, l’émission d’un 0 laisse le tableau identique, et que dans les autre cas, le réarrangement ne concerne que les premiers éléments du tableau. Par exemple, la séquence EEEEEA serait transformée en la suite 400001 ; le tableau évoluerait comme suit : Indice 0 1 2 3 4 5 6 . . . 25 État initial A B C D E F G ... Z Tableau modifié par le premier E E A B C D F G ... Z Tableau conservé 4 par les 4 E suivants ... Tableau modifié par le A A E B C D F G ... Z Ce déplacement peut être gérer de manière très simple dans un accumulateur (mis en oeuvre au niveau du codage). Mémoriser les déplacements devient aisé et réduit la complexité de la fonction puisque j’ai pensé à un algorithme plus intuitif qui modifie la fonction f à chaque nouvel appel de la fonction d’encodage mais le nombre d’instruction devient assez vite ingérable. Fonction decode : – définition : | Fonction : decode : (’a -> int) -> ’a list -> ’a : | Resultat : Décode la liste d’unités grâce à une fonction de décodage par défaut. Cette fonction doit être la réciproque de celle utilisée par la fonction ’encode’ Il s’agit de la réciproque de la méthode utilisée, avec un système identique de déplacement en tête mais avec une fonction qui récupère un élément à partir d’un nombre. 8 Siphaxay Didier Programmation fonctionnelle CAML 2.4 Projet P1 Huffman Fonction build tree : – définition : | Fonction : build tree : ’a list -> ’a huffmantree : | Resultat : Prend une liste d’éléments et construit l’arbre de codage. Fig. 2 – Exemple d’arbre d’huffman Afin de créer cet arbre il faut dans un premier temps faire une première analyse de l’ensemble que l’on veut coder pour déterminer les multiplicités des différents éléments. Ensuite Créer Chaque ”Feuille” de cet arbre puis les fusionner en prenant compte du ”poids” (somme des occurences de tous ses fils) de chaque branche. Ainsi on équilibre l’arbre en fonction du nombre d’apparition de l’élément dans la liste à coder. Fonction encode : – définition : | Fonction : encode : ’a list -> ’a huffmantree * bool list : | Resultat : rend une liste d’éléments et renvoie le couple (arbre de codage de huffman, codage binaire de la liste) Pour cette fonction d’encodage on peut arbitrairement posé une condition pour l’élaboration des chemins vers les éléments de l’ensemble. En effet, on peut considérer que la branche gauche d’un noeud peut représenter le booléen false (0) et ainsi la branche droite vaudrait true (1). Fig. 3 – Exemple d’arbre d’huffman + chemins On prend donc chaque élément à coder et on extrait la liste de booléens qui mène à ce dernier. 9 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Fonction decode : – définition : | Fonction : decode : ’a huffmantree * bool list -> ’a list : | Resultat : Prend un arbre de codage de huffman et une liste de booléens et reconstruit la liste d’éléments décodée Grâce à la liste de booléens, on parcourt l’arbre d’huffman selon donc l’ordre arbitraire que l’on s’est fixé auparavant. On procède booléen par booléen pour avancer dans l’arbre et dès que l’on tombe sur une feuille alors on renvoie cet élément. Par contre si cette liste ne se termine pas à une feuille alors elle était mal construite (exception à gérer). Ainsi on reconstitue la liste d’origine. 3 Codage Dans cette section, j’ai décidé de ne mettre que les algorithmes généraux des différentes fonctions implémentées dans chaque module. Pour raffiner ou mieux comprendre, je vous conseille de regarder les commentaires des fonctions numérotées dans le code source. (n) : Chaque item numéroté représente une sous-fonction auxiliaire (récursive non-élémentaire) implémentée dans le cadre de la fonction générale. Leur profil est disponible et permet de mieux comprendre le typage adopté dans cette solution (cf. Annexe). 3.1 Burrows Wheeler Fonction encode : – raffinage : (1) Créer la liste des permutations circulaires à partir de la liste d’origine (2) Trier cette liste par ordre croissant (3) Chercher la position du mot d’origine dans cette liste de liste (4) Récupérer les derniers éléments de chaque liste dans cette liste de liste pour former la liste codée (1) (2) (3) (4) encode aux : ’a list -> ’a list -> ’a list list trif : (’a -> ’a -> bool) -> ’a list -> ’a list n fin : ’a list list -> ’a list -> int inclus dans la fonction encode Choix des types des fonctions auxiliaires : Il nous fallait un type pour représenter l’ensemble des permutation circulaires de la liste à coder. Le plus judicieux fut compte tenu des contraintes de prendre des listes de liste d’éléments qui représente en somme une sorte de matrice muni de permutation et dans laquelle la dernière colone représente le mot codé. On récupère par ailleurs l’entier désignant la position de la liste d’origine après le tri des lignes de la matrice, dans la fonction n fin. Fonction decode : – raffinage : (1) Créer la liste classée à partir de la liste à décoder (2) Créer la liste des paires (élément, n-ieme occurence) avec leur indice absolu (3) Rechercher le premier élément à partir de l’indice de Mettre cet élément dans une liste auxiliaire (4) Tantque la liste n’est pas vide faire (5) Rechercher récursivement chaque élément suivant de Mettre cet élément dans la liste auxiliaire Passer à l’élément suivant Fin tanque Récupérer la liste décodée à partir de la liste auxilaire 10 dans les 2 listes précédentes départ la liste ré-ordonnée Siphaxay Didier Programmation fonctionnelle CAML (1) (2) (3) (4) (5) Projet P1 occurence : ’a -> int -> ’a list -> int creer paires : ’a list -> ’a list -> ((’a*int)*(’a*int)*int) recherche : int -> ’a list -> (’a*int) re ordonner : ’a list -> int -> (’a*int) list suivant : (’a*int) -> ’a list -> (’a*int) Choix des types des fonctions auxiliaires : Pour effectuer les recherches on doit créer un type qui permet de connaı̂tre la n-ieme occurence de l’élémnent sur lequel on est positionné. Donc c’est le type proposé dans l’algorithme qui se code ainsi : ((’a*int)*(’a*int)*int). Pour que la fonction suivant puisse passer récursivement d’un élément à un autre j’ai privilégié un le passage de la paire pour ne faire qu’une seule comparaison logique (bien que la machine fait la comparaison terme à terme). Pour illustrer cette naviguation dans la liste des paires voici le code proposé pour retrouvé l’ordre de la liste d’origine. Listing 3: Exemple du code de la recomposition de la liste d’origine (∗−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− | Fonction : re ordonner : ’ a list −$>$ int −$>$ (’a∗int) list : | Parametres : | l : ’ a list : liste creer a partir de la fonction creer paires | pabs : int : position absolue | Resultat : Renvoie liste des paires qui se suivent pour former la liste decode | Semantique : | Dans la liste des paires on recherche la premiere paire designe par | sa position absolue puis recursivement les suivants jusqu’a reformer la liste | prealablement encodee encore muni des postions relatives. | Complexité : Ø(n2) −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−∗) let re ordonner l pabs = let lg = List.length l in let rec ord aux n pattern = match n with | n when n=lg −$>$ [(suivant pattern l)] | n when n=1 −$>$ let g = recherche pabs l in g :: ord aux (n+1) g | n −$>$ let g = suivant pattern l in g :: ord aux (n+1) g in ord aux 1 (recherche pabs l );; 11 Siphaxay Didier Programmation fonctionnelle CAML 3.2 Projet P1 Move to Front Fonction encode : - raffinage : Tantque la liste n’est pas vide faire si l’élément en cours n’est pas dans la liste auxiliaire alors (1) Appliquer la fonction f à cet élément auquel il faut ajouter le nombre ... ... d’éléments qui lui sont supérieurs dans la liste auxilaire Mettre en t^ ete cet élément dans la liste auxilaire sinon (2) Récupérer la position de l’élément dans la liste auxiliaire (3) Déplacer cet élément en t^ ete de liste auxiliaire fin si Passer à l’élément suivant fin tantque (1) nb elem sup a : ’a -> ’a list -> int (2) position : ’a -> ’a list -> int (3) movetofirst : ’a -> ’a list -> ’a list -> ’a list Je ne réalise que des opérations quasi-élémentaires puisque j’ai simplifier la mise en oeuvre déployée dans la conception (première méthode) avec l’apparition d’un accumulateur gérant les déplacement en tête de liste. Fonction decode : - raffinage : Tantque la liste n’est pas vide faire Appliquer la fonction f(modifié ou pas) sur cet élément (1) Modifier la fonction f de façon à retenir toute les modifications précédentes Passer à l’élément suivant fin tantque (1) mtf int : (int -> ’a) -> int -> int -> ’a Pour bien enregistrer les modifications sur la fonction f grâce à la fonction mtf int dont le code est dévoilé ici, il faut passer non pas la fonction mtf int mais sa version partielle avec son premier argument. Ce dernier est celui qui retiendra toutes les modifications effectuées sur la liste. 12 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Listing 4: Exemple du code de la modification de la fonction f (∗−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− | Fonction : mtf int : ( int −$>$ ’a) −$>$ int −$>$ int −$>$ ’a : | Parametres : | f : int −$>$ ’a: fonction de codage (exemple Char.chr) | a : int : entier de la liste | n : int : entier suivant de la liste | Resultat : le resultat de la fonction f en fonction de a et n | Semantique : | si les 2 entiers qui se suivent sont identiques alors leur distance | est egale a 0 cela veut dire qu’ il faut recupere le code de l ’element a | sinon cela veut dire que que c’est l ’element code par n mais si l ’element | precedent ’a ’ est superieur à ’ n’ alors ca veut dire que que cet element | ’ a ’ a ete deplace en tete et que le code de ’n’ est donc modifie de 1 unite. | Complexité : Ø(constant), 2 operations de comparaisons −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−∗) let mtf int f a n = if n=0 then f a else if a < n then f n else f (n−1);; 3.3 Huffman Fonction build tree : - raffinage (1) Créer le multi-ensemble à partir de la liste de départ (2) Transformer ce multi-ensemble en Feuille (3) Trier ce multi-ensemble par occurence croissante Tant qu’il ne reste plus qu’un élément faire (4) Fusionner les 2 premiers éléments (5) Ré-insérer le resultat de la fusion dans la liste en conservant l’ordre Passer à l’élément suivant Fin tantque (6) Enlever les occurences devenues inutiles de cet arbre. cette étape définit le type ’a huffman (1) (2) (3) (4) (5) (6) occ : ’a list -> (int*’a) list transfo : ’a list -> ’a arbre list (dans la fonction build tree) trif : (’a -> ’a -> bool) -> ’a list -> ’a list (tri fusion) nouveau noeud : ’a arbre -> ’a arbre -> ’a arbre (dans la focntion build tree associé à ’fusionne’) insert : ’a arbre -> ’a arbre list -> ’a arbre list create huffman tree : ’a arbre ->’a huffman (dans la focntion build tree) Choix du typage : pour des raisons d’équilibrage je suis passé par un type intermédiaire le type ’a arbre qui possède les occurences à chaque de tous ses fils. Je travaille essentiellement sur ce type dans tout le code afin de bien gérer la construction de l’arbre d’huffman. Vous pourrez avoir plus de détails sur la contruction de cet arbre en regardant les commentaires du code source mais cet algorithme se suffit à lui-même. 13 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Fonction encode : - raffinage (*) Créer l’arbre de codage à partir du raffinage précédent (1) Créer la table des chemins de tous les éléments de l’arbre de codage Tant que la liste à coder n’est pas vide faire (2) Récupérer le chemin associé à l’élément présent dans l’arbre depuis la table des chemins Passer à l’élément suivant Fin tantque (*) réaliser par la fonction build tree. (1) tab chemins : ’a huffman -> ’a list -> (’a * bool list) list (2) research : ’a -> (’a * bool list) list -> bool list(associé à create bool list) Choix du typage : Pour simplifier les recherches, on met en oeuvre une pré-recherche (tab chemins) dans l’arbre d’huffman pour minimiser les appels à une fonction de recherche dans l’arbre. puis on récupère la liste de boléen ainsi former au passage de l’élément en cours de la liste à coder. Fonction decode : - raffinage Tantque la liste de booléens n’est pas vide faire Avancer d’un pas dans l’arbre en fonction du bit en cours Si cet élément est une Feuille alors Récupérer l’élément de cette Feuille Recommencer à partir du haut de l’arbre Sinon Continuer à avancer dans l’arbre Fin si Passer au booléen suivant Fin tantque Cet algorithme ne fait que parcourir sans opérations non-élémentaires l’arbre d’huffman grâce à la liste de booléen. On navigue dans cet arbre pas à pas afin de trouver la feuille conrrespondante à l’élément à décoder. 4 Tests Les tests sont d’une importance capitales autant pour le programmeur, le concepteur, le spécificateur et le client. C’est pour cela qu’il existe plusieurs types de tests : – Les tests unitaires (programmation) qui doivent respecter les profils des fonctions demandés – Les tests d’intégration (agencement des modules) qui permettent de tester si les différents modules arrivent à communiquer entre eux – Les tests usines qui rassemblent les test globaux de l’application livrée – La recette : test évaluant le résultat à ce qu’attendait le client – Les tests site qui sont là pour finaliser le contrat en vérifiant que l’application installée fonctionne à l’identique par rapport au test usines Les test unitaires ont été codé dans le code sous sous forme de commentaires. J’ai ajouté quelques tests d’intégration au sein de chaque module. Le test global se fera avec une série de fichier que j’aurai préparé et dont je donnerai le listing en annexes (cf. Annexe 1). La recette et le test site (réalisé le 12/12/2007) se feront donc avec les fichiers composants le listing précédent. 14 Siphaxay Didier Programmation fonctionnelle CAML 4.1 Pour – – – Projet P1 Méthodes de test valider un test quelque soit le niveau, il faut respecter différents paramètres, ici : le type de donnée en entrée (généricité) les propriétés des données les capacités physiques de la machines, . . . Organisation des tests : Il faut vérifié en général plusieurs type de cas : – Cas aux bornes (limites) : qui concerne souvent les objets vide ou muni d’un seul élément ou au borne de l’ensemble considéré – Cas d’exception : qui concerne tous les cas que vous auriez pu éventuellement jugé comme exceptionnel (cas impossible en théorie à gérer) – Cas généraux : des tests génériques (les types aussi dans ce cas précis) testant le bon fonctionnement de la fonction ou de l’application Test unitaires Burrows Wheeler – Cas aux bornes (limites) : les listes vides et les ensemble muni d’un seul élément ou trop grand – Cas d’exception : (decode seulement) un indice hors des bornes (0..lenght l) – Cas généraux : le test de base avec tous les types de base possibles (bool, int, float, string, char, ...) Move-to-Front – Cas aux bornes (limites) : les listes vides et les ensemble muni d’un seul élément ou trop grand – Cas d’exception : aucun – Cas généraux : le test de base avec tous les types de base possibles (bool, int, float, string, char, ...) Huffman – Cas aux bornes (limites) : les listes vides et les ensemble muni d’un seul élément ou trop grand – Cas d’exception : (decode seulement) une liste booléene mal contruite – Cas généraux : le test de base avec tous les types de base possibles (bool, int, float, string, char, ...) Test d’intégration dans chaque module – Cas aux bornes (limites) : les listes vides et les ensemble muni d’un seul élément ou trop grand – Cas d’exception : aucun – Cas généraux : le test de base avec tous les types de base possibles (bool, int, float, string, char, ...) Test globaux – Cas aux bornes (limites) : fichiers vides ou avec un seul élément ou trop grand – Cas d’exception : un fichier contenant un type non valide – Cas généraux : le test de base avec des fichiers de plus ou moins grandes tailles (test de performance!) Les tests usines (sur les machines de l’ENSEEIHT) ont été effectué et les résultats sont dans les annexes. On remarque bien que c’est la fonction encode de burrows-wheeler qui prend le plus de temps puisque je vous l’ai déjà expliqué, il faut générer la matrice des permutations. Cette étape prends de plus en plus de temps dès lors que l’on augmente la taille de la liste passée en paramètre. Une version itérative de cette fonction réduirai le temps d’éxécution. Vous le remarquerez j’ai aussi testé des cas où les fichiers d’entrées sont incorrects de part leur type : ce qui provoque une erreur dans le programme. De plus je peux aussi encodé des fichiers vides et des fichiers ”random” avec la commande dd sur le /dev/zero et le /dev/urandom qui permet de générer des fichiers remplis de zéro binaire respectivement des données aléatoires, très utile pour tester nos fonctions. 15 Siphaxay Didier Programmation fonctionnelle CAML 5 5.1 Projet P1 Conclusion Travail effectué En conclusion après une série de tests réussis, mon programme fonctionne du mieux que je pouvais l’espérer. je dirai que ce projet fut assez simple à réaliser compte tenu de l’abondance d’informations que l’on peut trouver sur l’internet. Du point de vue de mon travail, je trouve que les algorithmes que j’ai utilisé sont d’une complexité assez basse compte tenu des limitations du sujet qui imposait l’utilisation d’un style fonctionnel. Ainsi à travers ces algorithmes, on ne peut pas traiter des quantités de données trop importantes surtout en voyant ce que réalise la fonction encode de Burrows-Wheeler, en effet elle crée ce que je pourrai nommer une matrice de permutation de la liste qui contient n2 éléments. Cette étape peut mettre être interrompu car la mémoire physique de l’ordinateur ne le permet pas. (stack overflow) La recursivité impose alors le parcours intégrale de cette matrice alors que les références sont astucieuses dans ce cas : cela pourrai éviter des millions d’instructions à la machine et accélérer le processus. Néanmoins on se rend compte que si on a un nombre réduit d’élément dans cette matrice : les durées d’éxécution sont minimes. Une amélioration qui concerne le programme principal pourrai être de sollicité un ”découpeur” de fichier avec une certaine taille (à déterminer) pour ensuite lancer x processus qui chacun effectuerai l’encodage. Malheureusement, même si un gain de temps d’éxécution est prévisible, la compression ne serait plus aussi efficace car on définit à chaque fois un arbre qui contiendrait souvent un élément contenu dans un autre ... d’où encore un problème de gestion des doublons et de la taille de la donnée compressé. Donc en somme, réaliser le module gérant la transformé de Burrows-Wheeler dans un style itératif avec des tableaux et des références se l’alternative idéale. 5.2 Le langage Caml Concernant le langage Caml, beaucoup diront que c’est un langage dans lequel on ne se prend pas trop la tête avec la gestion dynamique des objets que l’on exploite, et je ne les contredirai pas en effet, il est plaisant de pouvoir programmer assez rapidement des algorithmes sans penser à des contraintes techniques assez pointues, ce qui est généralement le cas lorsque l’on programme en C. Cependant ce n’est pas un langage qui est adapté à une utilisation visant à augmenter les performances de votre programme, ce qui devient une question préoccupante dans l’industrie. Il peut par contre être un très bon langage pour des prototypes de logiciel. 5.3 Avis personel C’est la première fois que je réalise un travail de ce type. Il est vrai que j’ai déjà une expérience de la programmation mais je n’ai jamais vraiment réalisé quel algorithme était lié à certain type de compression. Ici je pense que c’est un plus pour moi puisque j’ai commencé à aborder le sujet, et que je vais peut-être approfondir mes recherches dessus dans le cas où j’aurai besoin de faire de la compression de donnée dans ma vie active. Ce projet fut intéressant à réaliser, même si les conditions imposées ne m’ont pas vraiment ravi. Il est vraiment sujet à une ouverture sur le domaine de l’optimisation de la gestion d’espace disque et cela est un plus pour notre vision en tant qu’informaticien. 16 Siphaxay Didier Programmation fonctionnelle CAML 6 Projet P1 Annexes 6.1 Listing des fichiers de test (globaux) Listing 5: Liste des fichiers à utiliser lors des test globaux <<Dans le dossier FichierTest>> vide. txt −− fichier vide zero. txt −− fichier de 5Ko remplie de zero binaire ( crée à partir de la commande dd) random.txt −− fichier de 5Ko remplie aléatoirement (crée à partir de la commande dd) France.txt −− texte de 2917 caractères nutellaraton . txt −− texte de 6666 caractères −− fichier de 100Ko remplie aléatoirement (crée à partir de la commande dd) trop grand.txt 6.2 Statistiques sur les tests usines Encodage : ./huffman -e ../FichierTest/nutellaraton.txt Burrows-Wheeler transformation ..... 58.6840999126sec Move to Front encoding ............. 5.66538000107sec Huffman encoding ................... 0.409476995468sec Compression rate achieved .......... 52/100 ./huffman -e ../FichierTest/vide.txt Burrows-Wheeler transformation ..... 2.86102294922e-06sec Move to Front encoding ............. 0.000128984451294sec Huffman encoding ................... 1.21593475342e-05sec Compression rate achieved .......... -1/100 ./huffman -e ../FichierTest/zero.txt Burrows-Wheeler transformation ..... 52.8612508774sec Move to Front encoding ............. 0.0151159763336sec Huffman encoding ................... 0.0109641551971sec Compression rate achieved .......... 13/100 ./huffman -e ../FichierTest/random.txt Burrows-Wheeler transformation ..... 29.701595068sec Move to Front encoding ............. 45.3592300415sec Huffman encoding ................... 0.836948871613sec Compression rate achieved .......... 127/100 ./huffman -e ../FichierTest/France.txt Burrows-Wheeler transformation ..... 10.0341439247sec Move to Front encoding ............. 1.84397697449sec Huffman encoding ................... 0.193593978882sec Compression rate achieved .......... 67/100 ./huffman -e ../FichierTest/nutellaraton.txt Burrows-Wheeler transformation ..... 52.9077839851sec Move to Front encoding ............. 5.11091113091sec Huffman encoding ................... 0.408653020859sec Compression rate achieved .......... 52/100 ./huffman -e ../FichierTest/trop grand.txt Fatal error: exception Stack overflow 17 Siphaxay Didier Programmation fonctionnelle CAML Projet P1 Décodage ./huffman -d ../FichierTest/vide.txt.encoded Huffman decoding ........................... 1.90734863281e-06sec Move to Front decoding ..................... 0.000166177749634sec Burrows-Wheeler inverse transformation ..... 1.19209289551e-05sec Resulting uncompressed file is in ”../FichierTest/vide.txt.decoded” ./huffman -d ../FichierTest/zero.txt.encoded Huffman decoding ........................... 0.00347995758057sec Move to Front decoding ..................... 1.77122306824sec Burrows-Wheeler inverse transformation ..... 10.0491089821sec Resulting uncompressed file is in ”../FichierTest/zero.txt.decoded” ./huffman -d ../FichierTest/random.txt.encoded Huffman decoding ........................... 0.0152530670166sec Move to Front decoding ..................... 2.71150994301sec Burrows-Wheeler inverse transformation ..... 14.5407299995sec Resulting uncompressed file is in ”../FichierTest/random.txt.decoded” ./huffman -d ../FichierTest/France.txt Fatal error: exception Failure(”input value: bad object”) ./huffman -d ../FichierTest/France.txt.encoded Huffman decoding ........................... 0.0042769908905sec Move to Front decoding ..................... 1.00814914703sec Burrows-Wheeler inverse transformation ..... 5.16888594627sec Resulting uncompressed file is in ”../FichierTest/France.txt.decoded” ./huffman -d ../FichierTest/nutellaraton.txt.encoded Huffman decoding ........................... 0.0136139392853sec Move to Front decoding ..................... 4.25166797638sec Burrows-Wheeler inverse transformation ..... 24.8214280605sec Resulting uncompressed file is in ”../FichierTest/nutellaraton.txt.decoded” ./huffman -d ../FichierTest/trop grand.txt Fatal error: exception Failure(”input value: bad object”) 18