From b3d7c1a32eff3c812ae07f8abc4278bfc826cdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre=20=E2=9C=8F?= Date: Fri, 21 Oct 2016 19:47:52 +0200 Subject: [PATCH] =?UTF-8?q?R=C3=A9organisation=20du=20code=20en=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Extraction des fonctions spécifiques à l'arbre dans huftree.h/.c * Extraction de la routine de compression dans compress.h/.c * Déplacement des fonctions d'affichage dans display.h/.c * Isolement des constantes dans common.h --- Makefile | 8 +- include/common.h | 4 + include/compress.h | 8 ++ include/display.h | 12 +++ include/huf.h | 49 ----------- include/huftree.h | 69 +++++++++++++++ src/compress.c | 83 ++++++++++++++++++ src/display.c | 48 ++++++++++ src/huf.c | 121 ------------------------- src/huftree.c | 213 +++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 47 +++------- 11 files changed, 453 insertions(+), 209 deletions(-) create mode 100644 include/common.h create mode 100644 include/compress.h create mode 100644 include/display.h delete mode 100644 include/huf.h create mode 100644 include/huftree.h create mode 100644 src/compress.c create mode 100644 src/display.c delete mode 100644 src/huf.c create mode 100644 src/huftree.c diff --git a/Makefile b/Makefile index 9dd0385..dbf81ec 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ CC=gcc -FLAGS=-Wall -std=c99 +CFLAGS=-Wall -std=c99 SRC=src PROG=compress -all: main.o huf.o - $(CC) $^ $(FLAGS) -o $(PROG) +all: main.o compress.o huftree.o display.o + $(CC) $^ $(CFLAGS) -o $(PROG) %.o: src/%.c - $(CC) $(FLAGS) -c $^ + $(CC) $(CFLAGS) -c $^ clean: rm -f $(PROG) *.o diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..ffb8cde --- /dev/null +++ b/include/common.h @@ -0,0 +1,4 @@ +#define IS_VERBOSE TRUE +#define TRUE 1 +#define FALSE 0 +#define NUM_CHARS 256 diff --git a/include/compress.h b/include/compress.h new file mode 100644 index 0000000..db88b0b --- /dev/null +++ b/include/compress.h @@ -0,0 +1,8 @@ +#ifndef __IN303_COMPRESS_H__ +#define __IN303_COMPRESS_H__ + +#include "huftree.h" + +void compress(const char *filepath); + +#endif diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..d6ad615 --- /dev/null +++ b/include/display.h @@ -0,0 +1,12 @@ +#ifndef __IN303_DISPLAY_H__ +#define __IN303_DISPLAY_H__ + +#include "huftree.h" + +/** + * Afficher sur la sortie standard l'arbre passé + * en paramètre + */ +void printTree(HufTree tree); + +#endif diff --git a/include/huf.h b/include/huf.h deleted file mode 100644 index 64282d3..0000000 --- a/include/huf.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef __FREQ_H__ -#define __FREQ_H__ - -#include - -#define TRUE 1 -#define FALSE 0 -#define NUM_CHARS 256 - -// structure représentant un sommet d'un arbre de Huffman -typedef struct Vertex Vertex; -struct Vertex { - // le double `freq` indique la fréquence d'apparition de la lettre - // du sommet dans le corpus, ou bien la somme des fréquences de ses fils - double frequency; - - // le caractère `value` est le caractère du corpus que ce sommet - // représente (caractère ASCII 0 - 255) - char value; - - // pointe vers le sommet parent à celui-ci, ou NULL s'il n'a pas de parent, - // comme par exemple pour la racine et les sommets en cours de traitement - Vertex* parent; - - // pointe vers les deux enfants de ce sommet, ou NULL s'il n'a pas - // d'enfants, comme par exemple pour les feuilles - Vertex *child_l, *child_r; -}; - -// structure représentant un arbre de Huffman -typedef struct Tree Tree; -struct Tree { - // quantité de sommets dans l'arbre. Le tableau des sommets contient - // exactement `size` éléments - size_t size; - - // tableau contenant les sommets de l'arbre, liés à leur parent, - // excepté pour la racine - Vertex* vertices; -}; - -// calcule un tableau des fréquences des caractères dans le corpus, -// indexé par la valeur numérique ASCII de chaque caractère -double* computeFrequencies(const char* filepath); - -// déduit d'un tableau de fréquences de caractères un arbre de Huffman -Tree buildTree(double* frequencies); - -#endif diff --git a/include/huftree.h b/include/huftree.h new file mode 100644 index 0000000..4448103 --- /dev/null +++ b/include/huftree.h @@ -0,0 +1,69 @@ +#ifndef __IN303_HUFTREE_H__ +#define __IN303_HUFTREE_H__ + +#include + +/** + * Représente un des sommets d'un arbre de Huffman + */ +typedef struct HufVertex HufVertex; +struct HufVertex { + // Indique la fréquence d'apparition de la lettre du sommet + // dans le corpus original, ou bien la somme des fréquences + // de ses enfants (si ce n'est pas une feuille) + double frequency; + + // Caractère du corpus original associé à ce sommet. Uniquement + // valable pour les feuilles. Pour les autres sommets, la valeur + // de (character) n'est pas garantie + char character; + + // Pointeurs vers les deux enfants de ce sommet. Ils valent tous + // deux NULL si le sommet est une feuille. À noter qu'un sommet + // a toujours 0 ou 2 enfants car un arbre de Huffman est binaire + HufVertex *child_l, *child_r; +}; + +/** + * Représente un arbre de Huffman + */ +typedef struct HufTree HufTree; +struct HufTree { + // Pointeur sur la racine de l'arbre + HufVertex *root; + + // Quantité de sommets dans l'arbre + size_t size; +}; + +/** + * Construire un arbre de Huffman basé sur les fréquences + * de caractères passées dans `frequencies` + * + * (résultat à libérer avec `freeTree`) + */ +HufTree createTree(double *frequencies); + +/** + * Libérer la mémoire occupée par un arbre de Huffman + * généré par la fonction `createTree` + */ +void freeTree(HufTree tree); + +/** + * Associer à chaque feuille de l'arbre une étiquette unique basée + * sur sa position dans l'arbre. Aucune étiquette n'est ainsi préfixe + * d'une autre. Le tableau renvoyé associe chaque caractère ASCII + * à son préfixe, ou à NULL s'il n'est pas présent dans l'arbre + * + * (résultat à libérer avec `freeTreeLabels`) + */ +char** createTreeLabels(HufTree tree); + +/** + * Libérer la mémoire occupée par un tableau d'étiquettes renvoyé + * par la fonction `labelTree` + */ +void freeTreeLabels(char** labels); + +#endif diff --git a/src/compress.c b/src/compress.c new file mode 100644 index 0000000..3fea317 --- /dev/null +++ b/src/compress.c @@ -0,0 +1,83 @@ +#include "../include/compress.h" +#include "../include/common.h" +#include "../include/display.h" +#include +#include + +/** + * Calculer un tableau de fréquences d'apparition des + * caractères ASCII dans le fichier de chemin donné + * + * (résultat à libérer avec `free`) + */ +static double* _createFrequencies(const char*); + +double* _createFrequencies(const char *filepath) { + double *frequencies = malloc(NUM_CHARS * sizeof(*frequencies)); + int totalChars = 0; + + for (size_t i = 0; i < NUM_CHARS; i++) { + frequencies[i] = 0; + } + + // Ouverture du fichier en lecture seule, et comptage + // des occurences de chaque caractère ASCII ainsi que + // du nombre total de caractères + FILE *file = fopen(filepath, "r"); + char current; + + while ((current = fgetc(file)) != EOF) { + frequencies[(size_t) current]++; + totalChars++; + } + + fclose(file); + + // Conversion des effectifs des caractères en fréquences + for (size_t i = 0; i < NUM_CHARS; i++) { + frequencies[i] /= totalChars; + } + + return frequencies; +} + +void compress(const char *filepath) { + double *frequencies = _createFrequencies(filepath); + + if (IS_VERBOSE) { + printf("--- 1 : CALCUL DES FRÉQUENCES ---\n\n"); + double sum = 0; + + for (size_t i = 0; i < NUM_CHARS; i++) { + if (frequencies[i] != 0) { + sum += frequencies[i]; + printf("%1c (%3d) : %4f\n", (int) i, (int) i, frequencies[i]); + } + } + + printf("Total : %f\n\n", sum); + } + + HufTree tree = createTree(frequencies); + + if (IS_VERBOSE) { + printf("--- 2 : CONSTRUCTION DE L'ARBRE ---\n\n"); + printf("Arbre à %zu sommets\n\n", tree.size); + printTree(tree); + } + + char** labels = createTreeLabels(tree); + + if (IS_VERBOSE) { + printf("\n\n--- 3 : ATTRIBUTION DES CODES ---\n\n"); + for (size_t i = 0; i < NUM_CHARS; i++) { + if (labels[i] != NULL) { + printf("%1c (%3d) : %s\n", (int) i, (int) i, labels[i]); + } + } + } + + free(frequencies); + freeTree(tree); + freeTreeLabels(labels); +} diff --git a/src/display.c b/src/display.c new file mode 100644 index 0000000..92201b8 --- /dev/null +++ b/src/display.c @@ -0,0 +1,48 @@ +#include "../include/display.h" +#include "../include/common.h" +#include + +/** + * Affiche l'arborescence à partir du sommet donné, + * indenté sur le niveau donné + */ +static void _printVertex(HufVertex, int level, int is_first); + +void _printVertex(HufVertex vert, int level, int is_first) { + // affichage des n espaces d'indentation + for (int i = 0; i < level; i++) { + if (i < level - 1) { + printf("│ "); + } else { + printf("├───"); + } + } + + if (vert.child_l != NULL && vert.child_r != NULL) { + // affichage d'un noeud contenant des enfants + // un tel noeud n'a pas de nom, on utilise un caractère + // selon sa position + const char *name; + + if (level == 0) { + name = "▅"; + } else { + if (is_first) { + name = "┬"; + } else { + name = "┼"; + } + } + + printf("%s (%4f)\n", name, vert.frequency); + _printVertex(*vert.child_l, level + 1, TRUE); + _printVertex(*vert.child_r, level + 1, FALSE); + } else { + // c'est une feuille, affichage du sommet + printf("%c (%4f)\n", vert.character, vert.frequency); + } +} + +void printTree(HufTree tree) { + _printVertex(*tree.root, 0, TRUE); +} diff --git a/src/huf.c b/src/huf.c deleted file mode 100644 index b89d725..0000000 --- a/src/huf.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "../include/huf.h" -#include -#include -#include - -double* computeFrequencies(const char* filepath) { - double* frequencies = (double*) malloc(NUM_CHARS * sizeof(double)); - int totalChars = 0; - - // initialisation du tableau à 0 - for (size_t i = 0; i < NUM_CHARS; i++) { - frequencies[i] = 0; - } - - // parcours du fichier et comptage des caractères - FILE* file = fopen(filepath, "r"); - char current; - - while ((current = fgetc(file)) != EOF) { - frequencies[(size_t) current]++; - totalChars++; - } - - // conversion des effectifs en fréquences - for (size_t i = 0; i < NUM_CHARS; i++) { - frequencies[i] /= totalChars; - } - - return frequencies; -} - -Tree buildTree(double* frequencies) { - // comptage du nombre de caractères différents dans le fichier, - // il s'agit du nombre initial de sommets dans l'arbre binaire - size_t vertex_count = 0; - - for (size_t i = 0; i < NUM_CHARS; i++) { - if (frequencies[i] > 0) { - vertex_count++; - } - } - - // allocation d'un tableau de 2n + 1 sommets, le nombre total de - // sommets dans l'arbre binaire final - size_t tree_size = 2 * vertex_count - 1; - Vertex* vertices = (Vertex*) malloc(tree_size * sizeof(Vertex)); - - // remplissage initial du tableau avec un sommet pour chaque lettre - size_t next_available = 0; - - for (size_t i = 0; i < NUM_CHARS; i++) { - if (frequencies[i] > 0) { - vertices[next_available].frequency = frequencies[i]; - vertices[next_available].value = (char) i; - vertices[next_available].parent = NULL; - vertices[next_available].child_l = NULL; - vertices[next_available].child_r = NULL; - - next_available++; - } - } - - // remplissage du tableau avec les sommets virtuels - while (next_available < tree_size) { - // détermination des deux sommets de fréquence les plus faibles - // parmi tous les sommets n'ayant pas de parent - size_t i = 0, min_vert, min_vert_sec; - int is_init = FALSE, is_init_sec = FALSE; - double min_freq; - - for (; i < next_available; i++) { - if (vertices[i].parent == NULL) { - if (!is_init) { - // initialisation du premier sommet sans parent - min_vert = i; - min_freq = vertices[i].frequency; - is_init = TRUE; - } else if (!is_init_sec) { - // initialisation du second sommet sans parent, - // en s'assurant qu'ils sont dans le bon ordre - if (vertices[i].frequency < min_freq) { - size_t swap = min_vert; - min_vert = i; - min_vert_sec = swap; - min_freq = vertices[i].frequency; - } else { - min_vert_sec = i; - } - - is_init_sec = TRUE; - } else if (vertices[i].frequency < min_freq) { - // déplacement de l'ancien minimum en second minimum - // et remplacement par le nouveau minimum - min_vert_sec = min_vert; - min_vert = i; - } - } - } - - // création d'un sommet virtuel parent de ces deux sommets - // ce sommet est créé à la première position libre dans le tableau - vertices[next_available].frequency = - vertices[min_vert].frequency + vertices[min_vert_sec].frequency; - vertices[next_available].parent = NULL; - vertices[next_available].child_l = &vertices[min_vert]; - vertices[next_available].child_r = &vertices[min_vert_sec]; - - // assignation du nouveau parent et des enfants - vertices[min_vert].parent = &vertices[next_available]; - vertices[min_vert_sec].parent = &vertices[next_available]; - - next_available++; - } - - Tree tree = { - .size = tree_size, - .vertices = vertices - }; - - return tree; -} diff --git a/src/huftree.c b/src/huftree.c new file mode 100644 index 0000000..076bf00 --- /dev/null +++ b/src/huftree.c @@ -0,0 +1,213 @@ +#include "../include/huftree.h" +#include "../include/common.h" +#include +#include + +/** +* Trouver les deux sommets de valeur la plus faible parmi +* tous les sommets pointés par le tableau. Place l'indice +* du minimum dans `min` et de l'avant-dernier dans `sec`. +*/ +static void _findMinimalVertices(HufVertex**, size_t, size_t* min, size_t* sec); + +/** +* Libérer récursivement la mémoire occupée par le sommet +* donné ainsi que celle de tous ses enfants (s'il en a). +*/ +static void _freeTreeVertex(HufVertex*); + +/** +* Créer une nouvelle chaîne contenant la chaîne donnée +* suffixée du caractère donné +*/ +static char* _appendToString(char*, size_t, char); + +/** +* Remplit le tableau d'étiquettes données avec les étiquettes +* des feuilles trouvées dans la sous-partie de l'arbre dont +* le sommet donné est la racine. Toutes les étiquettes ajoutées +* au tableau seront préfixées de la chaîne passée en paramètre +*/ +static void _labelVertex(HufVertex, char**, char*, size_t); + +HufTree createTree(double* frequencies) { + // Comptage du nombre de caractères différents dans le fichier : + // il s'agit du nombre de feuilles dans l'arbre binaire + size_t leaves_count = 0; + + for (size_t i = 0; i < NUM_CHARS; i++) { + if (frequencies[i] > 0) { + leaves_count++; + } + } + + // Allocation d'un tableau de `leaves_count` pointeurs vers sommets. + // Initialement, ce tableau contient les feuilles du futur arbre. + // Chaque feuille correspond à un caractère du corpus original + HufVertex **remaining = malloc(leaves_count * sizeof(*remaining)); + size_t next_index = 0; + + for (size_t i = 0; i < NUM_CHARS; i++) { + if (frequencies[i] > 0) { + remaining[next_index] = malloc(sizeof(*remaining[next_index])); + + remaining[next_index]->frequency = frequencies[i]; + remaining[next_index]->character = (char) i; + remaining[next_index]->child_l = NULL; + remaining[next_index]->child_r = NULL; + + next_index++; + } + } + + // Coeur de l'algorithme. On itère jusqu'à ce que le tableau `remaining` + // ne contienne plus qu'un élément : la racine de l'arbre. À toute + // itération, `remaining` pointe sur les sommets qui doivent encore + // être traités (c-à-d les sommets sans parent), et `remaining_count` + // est le nombre de sommets à traiter + size_t remaining_count = leaves_count; + + while (remaining_count > 1) { + // Recherche des deux sommets A et B de valeurs les plus faibles + // parmi les sommets pointés par le tableau `remaining` + size_t min_vert_index, sec_min_vert_index; + + _findMinimalVertices( + remaining, remaining_count, + &min_vert_index, &sec_min_vert_index + ); + + HufVertex *min_vert = remaining[min_vert_index]; + HufVertex *sec_min_vert = remaining[sec_min_vert_index]; + + // Création d'un sommet parent P pour A et B + HufVertex *parent = malloc(sizeof(*parent)); + + parent->frequency = min_vert->frequency + sec_min_vert->frequency; + parent->child_l = min_vert; + parent->child_r = sec_min_vert; + + // Modification du tableau de pointeurs `remaining` pour + // faire sortir A et B, faire entrer P, et réduire la longueur + // de `remaining` de 1 sommet + remaining[min_vert_index] = parent; + remaining[sec_min_vert_index] = remaining[remaining_count - 1]; + remaining[remaining_count - 1] = NULL; + + remaining_count--; + } + + // Stockage de l'adresse vers la racine de l'arbre dans un HufTree. + // Il est désormais possible de désallouer `remaining`, car la seule + // connaissance de la racine permet de parcourir tout l'arbre + HufTree tree = { + .root = remaining[0], + .size = 2 * leaves_count - 1 + }; + + free(remaining); + return tree; +} + +void _findMinimalVertices( + HufVertex **vertices, size_t size, + size_t *min_index, size_t *sec_min_index +) { + // Initialisation de telle sorte qu'initialement + // on ait `freq(min_index) < freq(sec_min_index)` + if (vertices[0]->frequency < vertices[1]->frequency) { + *min_index = 0; + *sec_min_index = 1; + } else { + *min_index = 1; + *sec_min_index = 0; + } + + for (size_t i = 2; i < size; i++) { + double freq = vertices[i]->frequency; + + if (freq < vertices[*min_index]->frequency) { + // Sommet de valeur inférieure au minimum trouvé, la valeur + // devient le nouveau minimum, le minimum devient second minimum + *sec_min_index = *min_index; + *min_index = i; + } else if (freq < vertices[*sec_min_index]->frequency) { + // Sommet de valeur entre le minimum et le second minimum trouvé, + // la valeur devient le nouveau second minimum + *sec_min_index = i; + } + } +} + +void freeTree(HufTree tree) { + _freeTreeVertex(tree.root); + tree.root = NULL; +} + +void _freeTreeVertex(HufVertex *vert) { + if (vert->child_l != NULL && vert->child_r != NULL) { + _freeTreeVertex(vert->child_l); + _freeTreeVertex(vert->child_r); + } + + free(vert); +} + +char** createTreeLabels(HufTree input) { + char** labels = malloc(NUM_CHARS * sizeof(*labels)); + + // Initialisation des étiquettes à NULL + for (size_t i = 0; i < NUM_CHARS; i++) { + labels[i] = NULL; + } + + _labelVertex(*input.root, labels, NULL, 0); + return labels; +} + +void _labelVertex(HufVertex vertex, char** labels, char* label, size_t length) { + if (vertex.child_l != NULL && vertex.child_r != NULL) { + // Si le sommet a des enfants, poursuite de la récursion. + // Étiquetage de la partie gauche avec '...0' et de la partie + // droite avec '...1' + _labelVertex( + *vertex.child_l, labels, + _appendToString(label, length, '0'), + length + 1 + ); + + _labelVertex( + *vertex.child_r, labels, + _appendToString(label, length, '1'), + length + 1 + ); + + // Libération de l'étiquette passée en paramètre, + // qui a désormais été propagée dans les enfants + free(label); + } else { + // Si le sommet est une feuille, étiquetage du caractère + // associé avec l'étiquette passée en paramètre. Fin de la récursion + labels[(size_t) vertex.character] = label; + } +} + +char* _appendToString(char* orig, size_t length, char append) { + char* result = malloc((length + 2) * sizeof(*result)); + + if (orig != NULL) { + strcpy(result, orig); + } + + result[length] = append; + result[length + 1] = '\0'; + return result; +} + +void freeTreeLabels(char** labels) { + for (size_t i = 0; i < NUM_CHARS; i++) { + free(labels[i]); + } + + free(labels); +} diff --git a/src/main.c b/src/main.c index 2ca624c..7180831 100644 --- a/src/main.c +++ b/src/main.c @@ -1,43 +1,20 @@ -#include "../include/huf.h" +#include "../include/compress.h" #include int main(int argc, const char** argv) { - if (argc != 2) { - fprintf(stderr, "Usage : compress \n"); - fprintf(stderr, "Paramètre fichier manquant.\n"); - return 1; - } + if (argc != 2) { + fprintf(stderr, "Utilisation : compress \n"); - double* frequencies = computeFrequencies(argv[1]); - double sum = 0; + if (argc == 1) { + fprintf(stderr, "Paramètre fichier manquant.\n"); + } else { + fprintf(stderr, "Trop de paramètres.\n"); + } - printf("--- 1 : CALCUL DES FRÉQUENCES ---\n\n"); + return 1; + } - for (int i = 0; i < 256; i++) { - if (frequencies[i] != 0) { - sum += frequencies[i]; - printf("%1c (%3d) : %4f\n", i, i, frequencies[i]); - } - } + compress(argv[1]); - printf("Total : %f\n\n", sum); - printf("--- 2 : CONSTRUCTION DE L'ARBRE ---\n\n"); - - Tree tree = buildTree(frequencies); - printf("Arbre à %zu sommets\n\n", tree.size); - - for (int i = 0; i < tree.size; i++) { - Vertex* vertex = &tree.vertices[i]; - - printf( - "[%8p] Sommet %c (%4f) : parent %8p, enfants %8p %8p\n", - (void*) vertex, vertex->value, vertex->frequency, - (void*) vertex->parent, (void*) vertex->child_l, - (void*) vertex->child_r - ); - } - - printf("\n\n--- 3 : ATTRIBUTION DES CODES ---\n\n"); - - return 0; + return 0; }