diff --git a/inc/buffer.h b/inc/buffer.h index ba34269..60e964d 100644 --- a/inc/buffer.h +++ b/inc/buffer.h @@ -4,6 +4,8 @@ #include #include +#include "common.h" + /** * Tampon permettant d'abstraire l'écriture dans un fichier bit par * bit au lieu d'octet par octet. Les bits sont vidés dans le fichier @@ -21,7 +23,7 @@ struct WriteBuffer { // Nombre d'octets écrits par le tampon d'écriture dans le // fichier, correspondant au nombre de vidages du tampon - uint64_t flushed_count; + bytecount flushed_count; // Fichier dans lequel le tampon d'éciture est vidé à l'appel // de `flushBuffer` ou au débordement @@ -49,7 +51,7 @@ void flushBuffer(WriteBuffer*); * Récupère le nombre de vidages effectués sur le tampon d'écriture, * correspondant au nombre d'octets écrits par le tampon dans le fichier */ -uint64_t getFlushedCount(WriteBuffer*); +bytecount getFlushedCount(WriteBuffer*); /** * Tampon permettant d'abstraire le lecture depuis un fichier bit par diff --git a/inc/common.h b/inc/common.h index 22736ac..2393aaa 100644 --- a/inc/common.h +++ b/inc/common.h @@ -1,3 +1,10 @@ +#ifndef __IN303_COMMON_H__ +#define __IN303_COMMON_H__ + #define TRUE 1 #define FALSE 0 #define NUM_CHARS 256 + +typedef long long unsigned int bytecount; + +#endif diff --git a/inc/display.h b/inc/display.h index 80ba5d1..6148971 100644 --- a/inc/display.h +++ b/inc/display.h @@ -3,6 +3,8 @@ #include +#include "common.h" + typedef struct HufVertex HufVertex; typedef HufVertex* HufTree; @@ -30,9 +32,9 @@ void printTree(HufTree tree); /** * Afficher sur la sortie standard le tableau associant les caractères - * à leur fréquence d'apparition passé en argument + * à leur effectif d'apparition passé en argument */ -void printFrequenciesTable(double*, size_t); +void printCountsTable(bytecount*, bytecount total, size_t); /** * Afficher sur la sortie standard le tableau associant les caractères diff --git a/inc/huftree.h b/inc/huftree.h index 9dd4f59..f46dbfb 100644 --- a/inc/huftree.h +++ b/inc/huftree.h @@ -4,6 +4,8 @@ #include #include +#include "common.h" + typedef struct WriteBuffer WriteBuffer; typedef struct ReadBuffer ReadBuffer; @@ -32,12 +34,12 @@ struct HufVertex { }; /** - * Construire un arbre de Huffman basé sur les fréquences - * de caractères passées dans `frequencies` + * Construire un arbre de Huffman basé sur les effectifs + * de caractères passés dans `counts` * * (résultat à libérer avec `freeTree`) */ -HufTree createTree(double* frequencies); +HufTree createTree(bytecount* counts); /** * Écrit une représentation binaire de l'arbre dans le diff --git a/src/buffer.c b/src/buffer.c index 2dadc78..d49220b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -27,16 +27,19 @@ void putBuffer(char bit, WriteBuffer* buffer) { void flushBuffer(WriteBuffer* buffer) { // FIXME: gérer les erreurs d'écriture - // Alignement des données à gauche de l'octet - buffer->data <<= 8 - buffer->pending_bits; - fputc(buffer->data, buffer->dest_file); + if (buffer->pending_bits) { + // Alignement des données à gauche de l'octet (si l'octet n'est pas + // complet, il sera aligné par des '0' à droite) + buffer->data <<= 8 - buffer->pending_bits; + fputc(buffer->data, buffer->dest_file); - buffer->data = 0; - buffer->pending_bits = 0; - buffer->flushed_count++; + buffer->data = 0; + buffer->pending_bits = 0; + buffer->flushed_count++; + } } -uint64_t getFlushedCount(WriteBuffer* buffer) { +bytecount getFlushedCount(WriteBuffer* buffer) { return buffer->flushed_count; } diff --git a/src/compress.c b/src/compress.c index 3d7b2e6..4a60c26 100644 --- a/src/compress.c +++ b/src/compress.c @@ -10,15 +10,12 @@ #include #include #include -#include /** - * Effectuer un comptage des caractères dans le corpus d'entrée. En déduire - * le tableau des fréquences d'apparition des caractères ASCII - * + * Effectuer un comptage des caractères dans le fichier d'entrée * (résultat à libérer avec `free`) */ -static double* _createFrequencies(FILE*, uint64_t*); +bytecount* _countCharacters(FILE*, bytecount* total); /** * Encoder le fichier d'entrée vers le fichier de sortie @@ -27,12 +24,12 @@ static double* _createFrequencies(FILE*, uint64_t*); */ static void _encodeFromTable(char**, FILE*, WriteBuffer*); -double* _createFrequencies(FILE* file, uint64_t* total) { - double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies)); +bytecount* _countCharacters(FILE* file, bytecount* total) { + bytecount* counts = malloc(NUM_CHARS * sizeof(*counts)); *total = 0; for (size_t i = 0; i < NUM_CHARS; i++) { - frequencies[i] = 0; + counts[i] = 0; } // Lecture du fichier caractère par caractère et comptage @@ -40,16 +37,11 @@ double* _createFrequencies(FILE* file, uint64_t* total) { while ((current = fgetc(file)) != EOF) { assert(current >= 0 && current < NUM_CHARS); - frequencies[current]++; + counts[current]++; (*total)++; } - // Conversion des effectifs des caractères en fréquences - for (size_t i = 0; i < NUM_CHARS; i++) { - frequencies[i] /= *total; - } - - return frequencies; + return counts; } void _encodeFromTable(char** labels, FILE* source, WriteBuffer* output) { @@ -71,18 +63,17 @@ void _encodeFromTable(char** labels, FILE* source, WriteBuffer* output) { } void compress(FILE* source, FILE* dest) { - // ÉTAPE 1 : calcul des fréquences d'apparition de chaque caractère - // dans le fichier source. Ce programme prend le terme caractère au sens - // restreint d'octet. Dans le cas où l'on compresse des fichiers Unicode + // ÉTAPE 1 : calcul des effectifs de chaque caractère dans le fichier + // source. Ce programme prend le terme caractère au sens restreint + // d'octet. Dans le cas où l'on compresse des fichiers Unicode // utilisant des caractères sur plusieurs octets, on aura donc une // compression moins optimale. - // FIXME: éviter la division par zéro pour les fichiers vides printVerbose("Calcul des fréquences d'apparition des caractères.\n"); - uint64_t start_bytes = 0; - double* frequencies = _createFrequencies(source, &start_bytes); + bytecount original_count = 0; + bytecount* counts = _countCharacters(source, &original_count); if (isVerbose()) { - printFrequenciesTable(frequencies, NUM_CHARS); + printCountsTable(counts, original_count, NUM_CHARS); } // ÉTAPE 2 : construction d'un arbre de Huffman correspondant aux @@ -90,10 +81,10 @@ void compress(FILE* source, FILE* dest) { // FIXME: éviter les arbres malformés avec les fichiers ne contenant qu'un // seul type de caractère printVerbose("\nConstruction de l'arbre de Huffman.\n"); - HufTree tree = createTree(frequencies); + HufTree tree = createTree(counts); - free(frequencies); - frequencies = NULL; + free(counts); + counts = NULL; if (isVerbose()) { printTree(tree); @@ -116,7 +107,7 @@ void compress(FILE* source, FILE* dest) { printVerbose("\nÉcriture des données compressées.\n"); // - Entier 64 bits : nombre d'octets dans le fichier originel - fwrite(&start_bytes, sizeof(start_bytes), 1, dest); + fwrite(&counts, sizeof(uint64_t), 1, dest); // - Arbre linéarisé : arbre de Huffman permettant le calcul de la clef writeTree(tree, &output); @@ -128,11 +119,12 @@ void compress(FILE* source, FILE* dest) { flushBuffer(&output); // ÉTAPE 5 : affichage des statistiques de compression et fin - uint64_t end_bytes = sizeof(start_bytes) + getFlushedCount(&output); - double gain = ((double) start_bytes - end_bytes) / start_bytes * 100; + bytecount final_count = sizeof(uint64_t) + getFlushedCount(&output); + double gain = ((double) original_count - final_count) / + original_count * 100; - printVerbose("Taille originelle : %" PRIu64 " octets.\n", start_bytes); - printVerbose("Taille compressée : %" PRIu64 " octets.\n", end_bytes); + printVerbose("Taille originelle : %llu octets.\n", original_count); + printVerbose("Taille compressée : %llu octets.\n", final_count); printVerbose("Gain : %.2f %% !\n", gain); freeTree(tree); diff --git a/src/display.c b/src/display.c index ecb25fb..e4c067d 100644 --- a/src/display.c +++ b/src/display.c @@ -45,11 +45,16 @@ int isVerbose() { } void printTree(HufTree tree) { - // Allocation d'un tampon suffisamment grand pour contenir - // les indentations des sommets les plus profonds de l'arbre - wchar_t* buffer = malloc(_treeDepth(tree) * 4 * sizeof(*buffer)); - _subPrintTree(tree, buffer, 0); - free(buffer); + if (tree == NULL) { + // Cas particulier : arbre vide + printVerbose("(Arbre vide.)\n"); + } else { + // Allocation d'un tampon suffisamment grand pour contenir + // les indentations des sommets les plus profonds de l'arbre + wchar_t* buffer = malloc(_treeDepth(tree) * 4 * sizeof(*buffer)); + _subPrintTree(tree, buffer, 0); + free(buffer); + } } void _subPrintTree(HufTree tree, wchar_t* buffer, int length) { @@ -95,23 +100,20 @@ int _treeDepth(HufTree tree) { } } -void printFrequenciesTable(double* table, size_t size) { - printVerbose("┌─┬────┬─────────┐\n"); - printVerbose("│C│code│fréquence│\n"); - printVerbose("├─┼────┼─────────┤\n"); - - double sum = 0; +void printCountsTable(bytecount* table, bytecount total, size_t size) { + printVerbose("┌─┬────┬────────┐\n"); + printVerbose("│C│code│effectif│\n"); + printVerbose("├─┼────┼────────┤\n"); for (size_t i = 0; i < size; i++) { if (table[i] != 0) { - printVerbose("│%c│%4d│%9.4lf│\n", _safeChar(i), (int) i, table[i]); - sum += table[i]; + printVerbose("│%c│%4d│%8llu│\n", _safeChar(i), (int) i, table[i]); } } - printVerbose("├─┴────┼─────────┤\n"); - printVerbose("│ Total│%9.4lf│\n", sum); - printVerbose("└──────┴─────────┘\n"); + printVerbose("├─┴────┼────────┤\n"); + printVerbose("│ Total│%8llu│\n", total); + printVerbose("└──────┴────────┘\n"); } void printLabelsTable(char** table, size_t size) { diff --git a/src/huftree.c b/src/huftree.c index c4957ef..ce0d633 100644 --- a/src/huftree.c +++ b/src/huftree.c @@ -27,13 +27,13 @@ static char* _appendToString(char*, size_t, char); */ static void _labelVertex(HufVertex, char**, char*, size_t); -HufTree createTree(double* frequencies) { +HufTree createTree(bytecount* counts) { // 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) { + if (counts[i] > 0) { leaves_count++; } } @@ -45,11 +45,11 @@ HufTree createTree(double* frequencies) { size_t next_index = 0; for (int i = 0; i < NUM_CHARS; i++) { - if (frequencies[i] > 0) { + if (counts[i] > 0) { remaining[next_index] = malloc(sizeof(*remaining[next_index])); remaining[next_index]->name = i; - remaining[next_index]->frequency = frequencies[i]; + remaining[next_index]->frequency = counts[i]; remaining[next_index]->child_l = NULL; remaining[next_index]->child_r = NULL; @@ -94,9 +94,21 @@ HufTree createTree(double* frequencies) { remaining_count--; } - // Il est désormais possible de désallouer `remaining`, car la seule - // connaissance de la racine permet de parcourir tout l'arbre - HufTree tree = remaining[0]; + HufTree tree; + + if (leaves_count == 0) { + // Cas particulier : aucun caractère dans le fichier, dans ce + // cas l'arbre est l'arbre vide + tree = NULL; + } else { + // Sinon, la racine de l'arbre est le dernier sommet encore + // dans le tableau `remaining` + tree = remaining[0]; + } + + // Il est désormais possible de désallouer les pointeurs sur les + // sommets de l'arbre, car la seule connaissance de la racine permet + // d'accéder à tout l'arbre free(remaining); return tree; } @@ -132,6 +144,12 @@ void _findMinimalVertices( } void writeTree(HufTree tree, WriteBuffer* buffer) { + if (tree == NULL) { + // Écriture de l'arbre vide dans le fichier : aucun + // bit n'est ajouté + return; + } + if (tree->child_l != NULL && tree->child_r != NULL) { // Bit "1" indiquant que le sommet a des enfants putBuffer(1, buffer); @@ -177,7 +195,7 @@ HufTree readTree(ReadBuffer* buffer) { } void freeTree(HufTree tree) { - if (tree->child_l != NULL && tree->child_r != NULL) { + if (tree != NULL && tree->child_l != NULL && tree->child_r != NULL) { freeTree(tree->child_l); freeTree(tree->child_r); } @@ -193,7 +211,10 @@ char** createTreeLabels(HufTree input) { labels[i] = NULL; } - _labelVertex(*input, labels, NULL, 0); + if (input != NULL) { + _labelVertex(*input, labels, NULL, 0); + } + return labels; } diff --git a/src/main.c b/src/main.c index 4dd541f..c7bdb8d 100644 --- a/src/main.c +++ b/src/main.c @@ -65,15 +65,15 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { default: argp_error( - state, "Trop d'arguments (l'argument %s est superflu)", - arg + state, "trop d'arguments (l'argument %s, numéro %d " + "est superflu)", arg, state->arg_num + 1 ); } break; case ARGP_KEY_END: if (state->arg_num < 1) { - argp_error(state, "Fichier d'entrée manquant"); + argp_error(state, "fichier d'entrée manquant"); } break; @@ -88,7 +88,7 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { * Nom, version du programme et adresse pour les bugs * (ces informations sont affichées dans l'aide) */ -const char *argp_program_version = "huffman v0.1"; +const char *argp_program_version = "huffman 0.1"; const char *argp_program_bug_address = ""; @@ -100,7 +100,7 @@ static struct argp_option options[] = { { .name = "decompress", .key = 'd', - .doc = "Décompresse SOURCE vers DEST au lieu de compresser" + .doc = "Décompresse SOURCE vers DEST" }, { .name = "verbose",