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:
parent
e013dca1bc
commit
fdbdeb1756
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,9 @@ 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
|
||||
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);
|
||||
|
||||
|
@ -35,8 +37,9 @@ void flushBuffer(WriteBuffer* buffer) {
|
|||
buffer->pending_bits = 0;
|
||||
buffer->flushed_count++;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t getFlushedCount(WriteBuffer* buffer) {
|
||||
bytecount getFlushedCount(WriteBuffer* buffer) {
|
||||
return buffer->flushed_count;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -45,12 +45,17 @@ int isVerbose() {
|
|||
}
|
||||
|
||||
void printTree(HufTree tree) {
|
||||
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) {
|
||||
if (tree->child_l != NULL && tree->child_r != NULL) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
if (input != NULL) {
|
||||
_labelVertex(*input, labels, NULL, 0);
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
|
10
src/main.c
10
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 =
|
||||
"<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",
|
||||
|
|
Loading…
Reference in New Issue