From fdbdeb175647337a634e7eb7ce4d4f41e05b8800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre=20=E2=9C=8F?= Date: Sun, 20 Nov 2016 14:33:55 +0100 Subject: [PATCH] Support du fichier vide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Utilisation des effectifs au lieu des fréquences pour éviter les divisions par 0. Ne pas vider le tampon s'il est déjà vide. Représentation par un arbre vide si aucun caractère. --- inc/buffer.h | 6 ++++-- inc/common.h | 7 +++++++ inc/display.h | 6 ++++-- inc/huftree.h | 8 +++++--- src/buffer.c | 17 ++++++++++------- src/compress.c | 52 +++++++++++++++++++++----------------------------- src/display.c | 34 +++++++++++++++++---------------- src/huftree.c | 39 ++++++++++++++++++++++++++++--------- src/main.c | 10 +++++----- 9 files changed, 105 insertions(+), 74 deletions(-) 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",