Support du fichier vide

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.
This commit is contained in:
Mattéo Delabre 2016-11-20 14:33:55 +01:00
parent e013dca1bc
commit fdbdeb1756
9 changed files with 105 additions and 74 deletions

View File

@ -4,6 +4,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include "common.h"
/** /**
* Tampon permettant d'abstraire l'écriture dans un fichier bit par * 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 * 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 // Nombre d'octets écrits par le tampon d'écriture dans le
// fichier, correspondant au nombre de vidages du tampon // 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 // Fichier dans lequel le tampon d'éciture est vidé à l'appel
// de `flushBuffer` ou au débordement // 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, * 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 * 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 * Tampon permettant d'abstraire le lecture depuis un fichier bit par

View File

@ -1,3 +1,10 @@
#ifndef __IN303_COMMON_H__
#define __IN303_COMMON_H__
#define TRUE 1 #define TRUE 1
#define FALSE 0 #define FALSE 0
#define NUM_CHARS 256 #define NUM_CHARS 256
typedef long long unsigned int bytecount;
#endif

View File

@ -3,6 +3,8 @@
#include <stdlib.h> #include <stdlib.h>
#include "common.h"
typedef struct HufVertex HufVertex; typedef struct HufVertex HufVertex;
typedef HufVertex* HufTree; typedef HufVertex* HufTree;
@ -30,9 +32,9 @@ void printTree(HufTree tree);
/** /**
* Afficher sur la sortie standard le tableau associant les caractères * 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 * Afficher sur la sortie standard le tableau associant les caractères

View File

@ -4,6 +4,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "common.h"
typedef struct WriteBuffer WriteBuffer; typedef struct WriteBuffer WriteBuffer;
typedef struct ReadBuffer ReadBuffer; typedef struct ReadBuffer ReadBuffer;
@ -32,12 +34,12 @@ struct HufVertex {
}; };
/** /**
* Construire un arbre de Huffman basé sur les fréquences * Construire un arbre de Huffman basé sur les effectifs
* de caractères passées dans `frequencies` * de caractères passés dans `counts`
* *
* (résultat à libérer avec `freeTree`) * (résultat à libérer avec `freeTree`)
*/ */
HufTree createTree(double* frequencies); HufTree createTree(bytecount* counts);
/** /**
* Écrit une représentation binaire de l'arbre dans le * Écrit une représentation binaire de l'arbre dans le

View File

@ -27,16 +27,19 @@ void putBuffer(char bit, WriteBuffer* buffer) {
void flushBuffer(WriteBuffer* buffer) { void flushBuffer(WriteBuffer* buffer) {
// FIXME: gérer les erreurs d'écriture // FIXME: gérer les erreurs d'écriture
// Alignement des données à gauche de l'octet if (buffer->pending_bits) {
buffer->data <<= 8 - buffer->pending_bits; // Alignement des données à gauche de l'octet (si l'octet n'est pas
fputc(buffer->data, buffer->dest_file); // complet, il sera aligné par des '0' à droite)
buffer->data <<= 8 - buffer->pending_bits;
fputc(buffer->data, buffer->dest_file);
buffer->data = 0; buffer->data = 0;
buffer->pending_bits = 0; buffer->pending_bits = 0;
buffer->flushed_count++; buffer->flushed_count++;
}
} }
uint64_t getFlushedCount(WriteBuffer* buffer) { bytecount getFlushedCount(WriteBuffer* buffer) {
return buffer->flushed_count; return buffer->flushed_count;
} }

View File

@ -10,15 +10,12 @@
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <stdint.h> #include <stdint.h>
#include <inttypes.h>
/** /**
* Effectuer un comptage des caractères dans le corpus d'entrée. En déduire * Effectuer un comptage des caractères dans le fichier d'entrée
* le tableau des fréquences d'apparition des caractères ASCII
*
* (résultat à libérer avec `free`) * (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 * 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*); static void _encodeFromTable(char**, FILE*, WriteBuffer*);
double* _createFrequencies(FILE* file, uint64_t* total) { bytecount* _countCharacters(FILE* file, bytecount* total) {
double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies)); bytecount* counts = malloc(NUM_CHARS * sizeof(*counts));
*total = 0; *total = 0;
for (size_t i = 0; i < NUM_CHARS; i++) { 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 // 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) { while ((current = fgetc(file)) != EOF) {
assert(current >= 0 && current < NUM_CHARS); assert(current >= 0 && current < NUM_CHARS);
frequencies[current]++; counts[current]++;
(*total)++; (*total)++;
} }
// Conversion des effectifs des caractères en fréquences return counts;
for (size_t i = 0; i < NUM_CHARS; i++) {
frequencies[i] /= *total;
}
return frequencies;
} }
void _encodeFromTable(char** labels, FILE* source, WriteBuffer* output) { 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) { void compress(FILE* source, FILE* dest) {
// ÉTAPE 1 : calcul des fréquences d'apparition de chaque caractère // ÉTAPE 1 : calcul des effectifs de chaque caractère dans le fichier
// dans le fichier source. Ce programme prend le terme caractère au sens // source. Ce programme prend le terme caractère au sens restreint
// restreint d'octet. Dans le cas où l'on compresse des fichiers Unicode // d'octet. Dans le cas où l'on compresse des fichiers Unicode
// utilisant des caractères sur plusieurs octets, on aura donc une // utilisant des caractères sur plusieurs octets, on aura donc une
// compression moins optimale. // 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"); printVerbose("Calcul des fréquences d'apparition des caractères.\n");
uint64_t start_bytes = 0; bytecount original_count = 0;
double* frequencies = _createFrequencies(source, &start_bytes); bytecount* counts = _countCharacters(source, &original_count);
if (isVerbose()) { if (isVerbose()) {
printFrequenciesTable(frequencies, NUM_CHARS); printCountsTable(counts, original_count, NUM_CHARS);
} }
// ÉTAPE 2 : construction d'un arbre de Huffman correspondant aux // É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 // FIXME: éviter les arbres malformés avec les fichiers ne contenant qu'un
// seul type de caractère // seul type de caractère
printVerbose("\nConstruction de l'arbre de Huffman.\n"); printVerbose("\nConstruction de l'arbre de Huffman.\n");
HufTree tree = createTree(frequencies); HufTree tree = createTree(counts);
free(frequencies); free(counts);
frequencies = NULL; counts = NULL;
if (isVerbose()) { if (isVerbose()) {
printTree(tree); printTree(tree);
@ -116,7 +107,7 @@ void compress(FILE* source, FILE* dest) {
printVerbose("\nÉcriture des données compressées.\n"); printVerbose("\nÉcriture des données compressées.\n");
// - Entier 64 bits : nombre d'octets dans le fichier originel // - 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 // - Arbre linéarisé : arbre de Huffman permettant le calcul de la clef
writeTree(tree, &output); writeTree(tree, &output);
@ -128,11 +119,12 @@ void compress(FILE* source, FILE* dest) {
flushBuffer(&output); flushBuffer(&output);
// ÉTAPE 5 : affichage des statistiques de compression et fin // ÉTAPE 5 : affichage des statistiques de compression et fin
uint64_t end_bytes = sizeof(start_bytes) + getFlushedCount(&output); bytecount final_count = sizeof(uint64_t) + getFlushedCount(&output);
double gain = ((double) start_bytes - end_bytes) / start_bytes * 100; double gain = ((double) original_count - final_count) /
original_count * 100;
printVerbose("Taille originelle : %" PRIu64 " octets.\n", start_bytes); printVerbose("Taille originelle : %llu octets.\n", original_count);
printVerbose("Taille compressée : %" PRIu64 " octets.\n", end_bytes); printVerbose("Taille compressée : %llu octets.\n", final_count);
printVerbose("Gain : %.2f %% !\n", gain); printVerbose("Gain : %.2f %% !\n", gain);
freeTree(tree); freeTree(tree);

View File

@ -45,11 +45,16 @@ int isVerbose() {
} }
void printTree(HufTree tree) { void printTree(HufTree tree) {
// Allocation d'un tampon suffisamment grand pour contenir if (tree == NULL) {
// les indentations des sommets les plus profonds de l'arbre // Cas particulier : arbre vide
wchar_t* buffer = malloc(_treeDepth(tree) * 4 * sizeof(*buffer)); printVerbose("(Arbre vide.)\n");
_subPrintTree(tree, buffer, 0); } else {
free(buffer); // 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) { void _subPrintTree(HufTree tree, wchar_t* buffer, int length) {
@ -95,23 +100,20 @@ int _treeDepth(HufTree tree) {
} }
} }
void printFrequenciesTable(double* table, size_t size) { void printCountsTable(bytecount* table, bytecount total, size_t size) {
printVerbose("┌─┬────┬─────────┐\n"); printVerbose("┌─┬────┬────────┐\n");
printVerbose("│C│code│fréquence│\n"); printVerbose("│C│code│effectif│\n");
printVerbose("├─┼────┼─────────┤\n"); printVerbose("├─┼────┼────────┤\n");
double sum = 0;
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
if (table[i] != 0) { if (table[i] != 0) {
printVerbose("│%c│%4d│%9.4lf│\n", _safeChar(i), (int) i, table[i]); printVerbose("│%c│%4d│%8llu│\n", _safeChar(i), (int) i, table[i]);
sum += table[i];
} }
} }
printVerbose("├─┴────┼────────\n"); printVerbose("├─┴────┼────────\n");
printVerbose("│ Total│%9.4lf│\n", sum); printVerbose("│ Total│%8llu│\n", total);
printVerbose("└──────┴────────\n"); printVerbose("└──────┴────────\n");
} }
void printLabelsTable(char** table, size_t size) { void printLabelsTable(char** table, size_t size) {

View File

@ -27,13 +27,13 @@ static char* _appendToString(char*, size_t, char);
*/ */
static void _labelVertex(HufVertex, char**, char*, size_t); 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 : // Comptage du nombre de caractères différents dans le fichier :
// il s'agit du nombre de feuilles dans l'arbre binaire // il s'agit du nombre de feuilles dans l'arbre binaire
size_t leaves_count = 0; size_t leaves_count = 0;
for (size_t i = 0; i < NUM_CHARS; i++) { for (size_t i = 0; i < NUM_CHARS; i++) {
if (frequencies[i] > 0) { if (counts[i] > 0) {
leaves_count++; leaves_count++;
} }
} }
@ -45,11 +45,11 @@ HufTree createTree(double* frequencies) {
size_t next_index = 0; size_t next_index = 0;
for (int i = 0; i < NUM_CHARS; i++) { 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] = malloc(sizeof(*remaining[next_index]));
remaining[next_index]->name = i; 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_l = NULL;
remaining[next_index]->child_r = NULL; remaining[next_index]->child_r = NULL;
@ -94,9 +94,21 @@ HufTree createTree(double* frequencies) {
remaining_count--; remaining_count--;
} }
// Il est désormais possible de désallouer `remaining`, car la seule HufTree tree;
// connaissance de la racine permet de parcourir tout l'arbre
HufTree tree = remaining[0]; 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); free(remaining);
return tree; return tree;
} }
@ -132,6 +144,12 @@ void _findMinimalVertices(
} }
void writeTree(HufTree tree, WriteBuffer* buffer) { 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) { if (tree->child_l != NULL && tree->child_r != NULL) {
// Bit "1" indiquant que le sommet a des enfants // Bit "1" indiquant que le sommet a des enfants
putBuffer(1, buffer); putBuffer(1, buffer);
@ -177,7 +195,7 @@ HufTree readTree(ReadBuffer* buffer) {
} }
void freeTree(HufTree tree) { 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_l);
freeTree(tree->child_r); freeTree(tree->child_r);
} }
@ -193,7 +211,10 @@ char** createTreeLabels(HufTree input) {
labels[i] = NULL; labels[i] = NULL;
} }
_labelVertex(*input, labels, NULL, 0); if (input != NULL) {
_labelVertex(*input, labels, NULL, 0);
}
return labels; return labels;
} }

View File

@ -65,15 +65,15 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) {
default: default:
argp_error( argp_error(
state, "Trop d'arguments (l'argument %s est superflu)", state, "trop d'arguments (l'argument %s, numéro %d "
arg "est superflu)", arg, state->arg_num + 1
); );
} }
break; break;
case ARGP_KEY_END: case ARGP_KEY_END:
if (state->arg_num < 1) { if (state->arg_num < 1) {
argp_error(state, "Fichier d'entrée manquant"); argp_error(state, "fichier d'entrée manquant");
} }
break; 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 * Nom, version du programme et adresse pour les bugs
* (ces informations sont affichées dans l'aide) * (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 = const char *argp_program_bug_address =
"<matteo.delabre@etu.umontpellier.fr>"; "<matteo.delabre@etu.umontpellier.fr>";
@ -100,7 +100,7 @@ static struct argp_option options[] = {
{ {
.name = "decompress", .name = "decompress",
.key = 'd', .key = 'd',
.doc = "Décompresse SOURCE vers DEST au lieu de compresser" .doc = "Décompresse SOURCE vers DEST"
}, },
{ {
.name = "verbose", .name = "verbose",