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 <stdint.h>
#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

View File

@ -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

View File

@ -3,6 +3,8 @@
#include <stdlib.h>
#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

View File

@ -4,6 +4,8 @@
#include <stdio.h>
#include <stdlib.h>
#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

View File

@ -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;
}

View File

@ -10,15 +10,12 @@
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
/**
* 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);

View File

@ -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) {

View File

@ -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;
}

View File

@ -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 =
"<matteo.delabre@etu.umontpellier.fr>";
@ -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",