Created
September 19, 2014 23:29
-
-
Save soaresfabricio/721a19d1c2d390c6e147 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* huffman.c | |
* Um simples compressor de arquivos usando árvores de Huffman. | |
* @ver 0.1 | |
* @autores: Fabrício Soares | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <time.h> | |
#include <string.h> | |
#include <math.h> | |
/** Definição do tipo de dados 'byte' | |
* 'unsigned char': É o tipo que consegue gravar no intervalo que vai de 0 a 255 bytes | |
*/ | |
typedef unsigned char byte; | |
/** Definição da árvore */ | |
typedef struct nodeArvore | |
{ | |
int frequencia; | |
byte c; | |
struct nodeArvore *esquerda; | |
struct nodeArvore *direita; | |
} nodeArvore; | |
/** Definição da fila de prioridade (implementada como lista simplesmente encadeada) */ | |
typedef struct nodeLista | |
{ | |
nodeArvore *n; | |
struct nodeLista *proximo; | |
} nodeLista; | |
typedef struct lista | |
{ | |
nodeLista *head; | |
int elementos; | |
} lista; | |
/** | |
* A função strdup é dependente de implementação nas plataformas não POSIX (Windows, etc) | |
* Segue uma implementação desta função como solução para o problema. | |
*/ | |
char *strdup(const char *s) | |
{ | |
char *p = malloc(strlen(s) + 1); | |
if (p) strcpy(p, s); | |
return p; | |
} | |
/** Função que faz alocação de memória e trata os ponteiros soltos acerca de nós da lista encadeada. | |
* Obs: cada nó da lista encadeada é conectado a um nó 'raiz' de árvore. | |
* @param: um nó de uma árvore. | |
*/ | |
nodeLista *novoNodeLista(nodeArvore *nArv) | |
{ | |
// Aloca memória | |
nodeLista *novo; | |
if ( (novo = malloc(sizeof(*novo))) == NULL ) return NULL; | |
// Adiciona a árvore ao nó | |
novo->n = nArv; | |
// Faz o campo próximo apontar para NULL | |
novo->proximo = NULL; | |
return novo; | |
} | |
/** Função que faz alocação de memória e trata os ponteiros soltos acerca de nós da árvore | |
* @param: o byte a ser gravado no nó, a frequencia do byte, ponteiros para os nós filhos | |
*/ | |
nodeArvore *novoNodeArvore(byte c, int frequencia, nodeArvore *esquerda, nodeArvore *direita) | |
{ | |
// Aloca memória | |
nodeArvore *novo; | |
if ( ( novo = malloc(sizeof(*novo)) ) == NULL ) return NULL; | |
// Atribui na árvore os valores passados como parâmetro | |
novo->c = c; | |
novo->frequencia = frequencia; | |
novo->esquerda = esquerda; | |
novo->direita = direita; | |
return novo; | |
} | |
/** Função que um novo nó na lista encadeada que representa a fila de prioridade. | |
* @param: um nó previamente criado, a lista que receberá o nó | |
*/ | |
void insereLista(nodeLista *n, lista *l) | |
{ | |
// Se a lista passada como parâmetro não tem um nó no início (vazia), insira o nó no início | |
if (!l->head) | |
{ | |
l->head = n; | |
} | |
// Se o campo 'frequência' do 'nó' parâmetro for menor que o campo 'frequência' do primeiro item (head) | |
// da lista, incluir o novo nó como head, e colocar o head antigo como next desse novo | |
else if (n->n->frequencia < l->head->n->frequencia) | |
{ | |
n->proximo = l->head; | |
l->head = n; | |
} | |
else | |
{ | |
// nó auxiliar que inicia apontando para o segundo nó da lista (head->proximo) | |
nodeLista *aux = l->head->proximo; | |
// nó auxiliar que inicia apontando para o primeiro nó da lista | |
nodeLista *aux2 = l->head; | |
// Laço que percorre a lista e insere o nó na posição certa de acordo com sua frequência. | |
// | |
// Se sabe que aux começa apontando para o segundo item da lista e aux2 apontando para o primeiro. | |
// Sendo assim, os ponteiros seguirão mudando de posição enquanto aux não for o fim da lista, | |
// e enquanto a frequência do nó apontado por aux for menor ou igual a frequência do 'nó' parâmetro. | |
while (aux && aux->n->frequencia <= n->n->frequencia) | |
{ | |
aux2 = aux; | |
aux = aux2->proximo; | |
} | |
// Se insere o nó na posição certa. | |
aux2->proximo = n; | |
n->proximo = aux; | |
} | |
// Incrementa quantidade de elementos | |
l->elementos++; | |
} | |
/** Função que 'solta' o nó apontado por 'head' da lista (o de menor frequência) | |
* (faz backup do nó e o desconecta da lista) | |
* @param: uma lista encadeada. | |
*/ | |
nodeArvore *popMinLista(lista *l) | |
{ | |
// Ponteiro auxilar que aponta para o primeiro nó da lista | |
nodeLista *aux = l->head; | |
// Ponteiro auxiliar que aponta para a árvore contida em aux (árvore do primeiro nó da lista) | |
nodeArvore *aux2 = aux->n; | |
// Aponta o 'head' da lista para o segundo elemento dela | |
l->head = aux->proximo; | |
// Libera o ponteiro aux | |
free(aux); | |
aux = NULL; | |
// Decrementa a quantidade de elementos | |
l->elementos--; | |
return aux2; | |
} | |
/** Função que conta a frequência de ocorrências dos bytes de um dado arquivo | |
* @param: um arquivo, uma lista de bytes | |
*/ | |
void getByteFrequency(FILE *entrada, unsigned int *listaBytes) | |
{ | |
byte c; | |
/*** | |
* | |
* fread( array/bloco de memoria , tamanho de cada elemento, quantos elementos, arquivo de entrada ) | |
* fread retorna a quantidade de blocos lidos com sucesso | |
* | |
* Faz a leitura de 1 bloco de tamanho 1 byte a partir do arquivo 'entrada' | |
* e salva no espaco de memoria de 'c'. | |
* | |
* Converte esse byte num valor decimal, acessa o bucket correspondente e incrementa o valor (frequência). | |
* | |
***/ | |
while (fread(&c, 1, 1, entrada) >= 1) | |
{ | |
listaBytes[(byte)c]++; | |
} | |
rewind(entrada); // "rebobina o arquivo" | |
} | |
#include <stdbool.h> | |
// Obtem o código começando no nó n, utilizando o byte salvo em 'c', preenchendo 'buffer', desde o bucket 'tamanho' | |
/** | |
/ Função recursiva que percorre uma árvore de huffman e para ao encontrar o byte procurado (c) | |
/ @param: nó para iniciar a busca, byte a ser buscado, buffer para salvar os nós percorridos, posição para escrever | |
**/ | |
bool pegaCodigo(nodeArvore *n, byte c, char *buffer, int tamanho) | |
{ | |
// Caso base da recursão: | |
// Se o nó for folha e o seu valor for o buscado, colocar o caractere terminal no buffer e encerrar | |
if (!(n->esquerda || n->direita) && n->c == c) | |
{ | |
buffer[tamanho] = '\0'; | |
return true; | |
} | |
else | |
{ | |
bool encontrado = false; | |
// Se existir um nó à esquerda | |
if (n->esquerda) | |
{ | |
// Adicione '0' no bucket do buffer correspondente ao 'tamanho' nodeAtual | |
buffer[tamanho] = '0'; | |
// fazer recursão no nó esquerdo | |
encontrado = pegaCodigo(n->esquerda, c, buffer, tamanho + 1); | |
} | |
if (!encontrado && n->direita) | |
{ | |
buffer[tamanho] = '1'; | |
encontrado = pegaCodigo(n->direita, c, buffer, tamanho + 1); | |
} | |
if (!encontrado) | |
{ | |
buffer[tamanho] = '\0'; | |
} | |
return encontrado; | |
} | |
} | |
// Algoritmo para construir a árvore de huffman, inspirado no seguinte pseudocódigo: | |
// http://www.cs.gettysburg.edu/~ilinkin/courses/Spring-2014/cs216/assignments/a8.html | |
// | |
// procedure BUILD-TREE(frequencies): | |
// pq ← make empty priority queue | |
// for each (symbol, key) in frequencies: | |
// h ← make a leaf node for the (symbol, key) pair | |
// INSERT(pq, h) | |
// | |
// n ← size[pq] | |
// for i = 1 to n-1: | |
// h1 ← POP-MIN(pq) | |
// h2 ← POP-MIN(pq) | |
// h3 ← MAKE-NODE(h1, h2) | |
// INSERT(pq, h3) | |
// | |
// return POP-MIN(pq) | |
/** Função que constrói a árvore de huffman | |
* @param: a fila de prioridade. | |
*/ | |
nodeArvore *BuildHuffmanTree(unsigned *listaBytes) | |
{ | |
// Lista com head apontando pra NULL e com campo 'elementos' valendo 0; | |
lista l = {NULL, 0}; | |
// Popula usando a array 'listaBytes' (que contém as frequências) uma lista encadeada de nós. | |
// Cada nó contém uma árvore. | |
for (int i = 0; i < 256; i++) | |
{ | |
if (listaBytes[i]) // Se existe ocorrência do byte | |
{ | |
// Insere na lista 'l' um nó referente ao byte i e sua respectiva frequência (guardada em listaBytes[i]). | |
// Faz os nós esquerdo e direito das árvores apontarem para NULL; | |
insereLista(novoNodeLista(novoNodeArvore(i, listaBytes[i], NULL, NULL)), &l); | |
} | |
} | |
while (l.elementos > 1) // Enquanto o número de elementos da lista for maior que 1 | |
{ | |
// Nó esquerdo chama a função popMinLista() que vai na lista e pega a árvore fixada no primeiro nó | |
// (que é a que contém a menor frequência) | |
nodeArvore *nodeEsquerdo = popMinLista(&l); | |
// Pega o outro nó que tem menor frequência | |
nodeArvore *nodeDireito = popMinLista(&l); | |
// Preenche com '#' o campo de caractere do nó da árvore. | |
// Preenche o campo 'frequência' com a soma das frequências de 'nodeEsquerdo' e 'nodeDireito'. | |
// Aponta o nó esquerdo para nodeEsquerdo e o nó direito para nodeDireito | |
nodeArvore *soma = novoNodeArvore( | |
'#', | |
nodeEsquerdo->frequencia + nodeDireito->frequencia, nodeEsquerdo, nodeDireito | |
); | |
// Reinsere o nó 'soma' na lista l | |
insereLista(novoNodeLista(soma), &l); | |
} | |
return popMinLista(&l); | |
} | |
/** Função que libera memória da árvore de huffman | |
* @param: nó de uma (sub)árvore. | |
*/ | |
void FreeHuffmanTree(nodeArvore *n) | |
{ | |
// Caso base da recursão, enquanto o nó não for NULL | |
if (!n) return; | |
else | |
{ | |
nodeArvore *esquerda = n->esquerda; | |
nodeArvore *direita = n->direita; | |
free(n); | |
FreeHuffmanTree(esquerda); | |
FreeHuffmanTree(direita); | |
} | |
} | |
/** Função que faz bitmasking no byte lido e retorna um valor booleano confirmando sua existência | |
* Ideia do bitmasking surgiu da leitura de http://ellard.org/dan/www/CS50-95/s10.html | |
* @param: arquivo para ler o byte, posição que se deseja mascarar o byte, byte a ser feita a checagem | |
*/ | |
int geraBit(FILE *entrada, int posicao, byte *aux ) | |
{ | |
// É hora de ler um bit? | |
(posicao % 8 == 0) ? fread(aux, 1, 1, entrada) : NULL == NULL ; | |
// Exclamação dupla converte para '1' (inteiro) se não for 0. Caso contrário, deixa como está (0) | |
// Joga '1' na casa binária 'posicao' e vê se "bate" com o byte salvo em *aux do momento | |
// Isso é usado para percorrer a árvore (esquerda e direita) | |
return !!((*aux) & (1 << (posicao % 8))); | |
} | |
/** Função para notificar ausência do arquivo. Encerra o programa em seguida. | |
*/ | |
void erroArquivo() | |
{ | |
printf("Arquivo nao encontrado\n"); | |
exit(0); | |
} | |
/** Função que comprime um arquivo utilizando a compressão de huffman | |
* @param: arquivo a comprimir, arquivo resultado da compressão | |
*/ | |
void CompressFile(const char *arquivoEntrada, const char *arquivoSaida) | |
{ | |
clock_t inicio, final; | |
double tempoGasto; | |
inicio = clock(); | |
unsigned listaBytes[256] = {0}; | |
// Abre arquivo do parâmetro arquivoEntrada no modo leitura de binário | |
FILE *entrada = fopen(arquivoEntrada, "rb"); | |
(!entrada) ? erroArquivo() : NULL == NULL ; | |
// Abre arquivo do parâmetro arquivoSaida no modo escrita de binário | |
FILE *saida = fopen(arquivoSaida, "wb"); | |
(!saida) ? erroArquivo() : NULL == NULL ; | |
getByteFrequency(entrada, listaBytes); | |
// Populando a árvore com a lista de frequência de bytes | |
nodeArvore *raiz = BuildHuffmanTree(listaBytes); | |
// Grava a lista de frequência nos primeiros 256 bytes do arquivo | |
fwrite(listaBytes, 256, sizeof(listaBytes[0]), saida); | |
// Move o ponteiro do stream 'saida' para a posição posterior ao tamanho de um unsigned int | |
// É aqui que posteriormente será salvo o valor da variável 'tamanho' | |
fseek(saida, sizeof(unsigned int), SEEK_CUR); | |
byte c; | |
unsigned tamanho = 0; | |
byte aux = 0; | |
/*** | |
* fread( array/bloco de memoria , tamanho de cada elemento, quantos elementos, arquivo de entrada ) | |
* fread retorna a quantidade de bytes lidos com sucesso | |
* | |
* Faz a leitura de 1 bloco de tamanho 1 byte a partir do arquivo 'entrada' | |
* e salva no espaco de memoria de 'c'. | |
***/ | |
while (fread(&c, 1, 1, entrada) >= 1) | |
{ | |
// Cria um buffer vazio | |
char buffer[1024] = {0}; | |
// Busca o código começando no nó 'raiz', utilizando o byte salvo em 'c', preenchendo 'buffer', desde o bucket deste último | |
pegaCodigo(raiz, c, buffer, 0); | |
// Laço que percorre o buffer | |
for (char *i = buffer; *i; i++) | |
{ | |
// Se o caractere na posição nodeAtual for '1' | |
if (*i == '1') | |
{ | |
// 2 elevado ao resto da divisão de 'tamanho' por 8 | |
// que é o mesmo que jogar um '1' na posição denotada por 'tamanho % 8' | |
//aux = aux + pow(2, tamanho % 8); | |
aux = aux | (1 << (tamanho % 8)); | |
} | |
tamanho++; | |
// Já formou um byte, é hora de escrevê-lo no arquivo | |
if (tamanho % 8 == 0) | |
{ | |
fwrite(&aux, 1, 1, saida); | |
// Zera a variável auxiliar | |
aux = 0; | |
} | |
} | |
} | |
// Escreve no arquivo o que sobrou | |
fwrite(&aux, 1, 1, saida); | |
// Move o ponteiro do stream para 256 vezes o tamanho de um unsigned int, a partir do início dele (SEEK_SET) | |
fseek(saida, 256 * sizeof(unsigned int), SEEK_SET); | |
// Salva o valor da variável 'tamanho' no arquivo saida | |
fwrite(&tamanho, 1, sizeof(unsigned), saida); | |
final = clock(); | |
tempoGasto = (double)(final - inicio) / CLOCKS_PER_SEC; | |
// Calcula tamanho dos arquivos | |
fseek(entrada, 0L, SEEK_END); | |
double tamanhoEntrada = ftell(entrada); | |
fseek(saida, 0L, SEEK_END); | |
double tamanhoSaida = ftell(saida); | |
FreeHuffmanTree(raiz); | |
printf("Arquivo de entrada: %s (%g bytes)\nArquivo de saida: %s (%g bytes)\nTempo gasto: %gs\n", arquivoEntrada, tamanhoEntrada / 1000, arquivoSaida, tamanhoSaida / 1000, tempoGasto); | |
printf("Taxa de compressao: %d%%\n", (int)((100 * tamanhoSaida) / tamanhoEntrada)); | |
fclose(entrada); | |
fclose(saida); | |
} | |
/** Função que descomprime um arquivo utilizando a compressão de huffman | |
* @param: arquivo a descomprimir, arquivo resultado da descompressão | |
*/ | |
void DecompressFile(const char *arquivoEntrada, const char *arquivoSaida) | |
{ | |
clock_t inicio, final; | |
double tempoGasto; | |
inicio = clock(); | |
unsigned listaBytes[256] = {0}; | |
// Abre arquivo do parâmetro arquivoEntrada no modo leitura de binário | |
FILE *entrada = fopen(arquivoEntrada, "rb"); | |
(!entrada) ? erroArquivo() : NULL == NULL ; | |
// Abre arquivo do parâmetro arquivoSaida no modo escrita de binário | |
FILE *saida = fopen(arquivoSaida, "wb"); | |
(!saida) ? erroArquivo() : NULL == NULL ; | |
// Lê a lista de frequência que encontra-se nos primeiros 256 bytes do arquivo | |
fread(listaBytes, 256, sizeof(listaBytes[0]), entrada); | |
// Constrói árvore | |
nodeArvore *raiz = BuildHuffmanTree(listaBytes); | |
// Lê o valor dessa posição do stream para dentro da variável tamanho | |
unsigned tamanho; | |
fread(&tamanho, 1, sizeof(tamanho), entrada); | |
unsigned posicao = 0; | |
byte aux = 0; | |
// Enquanto a posicao for menor que tamanho | |
while (posicao < tamanho) | |
{ | |
// Salvando o nodeArvore que encontramos | |
nodeArvore *nodeAtual = raiz; | |
// Enquanto nodeAtual não for folha | |
while ( nodeAtual->esquerda || nodeAtual->direita ) | |
{ | |
nodeAtual = geraBit(entrada, posicao++, &aux) ? nodeAtual->direita : nodeAtual->esquerda; | |
} | |
fwrite(&(nodeAtual->c), 1, 1, saida); | |
} | |
FreeHuffmanTree(raiz); | |
final = clock(); | |
tempoGasto = (double)(final - inicio) / CLOCKS_PER_SEC; | |
fseek(entrada, 0L, SEEK_END); | |
double tamanhoEntrada = ftell(entrada); | |
fseek(saida, 0L, SEEK_END); | |
double tamanhoSaida = ftell(saida); | |
printf("Arquivo de entrada: %s (%g bytes)\nArquivo de saida: %s (%g bytes)\nTempo gasto: %gs\n", arquivoEntrada, tamanhoEntrada / 1000, arquivoSaida, tamanhoSaida / 1000, tempoGasto); | |
printf("Taxa de descompressao: %d%%\n", (int)((100 * tamanhoSaida) / tamanhoEntrada)); | |
fclose(saida); | |
fclose(entrada); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
// Caso os parâmetros informados sejam insuficientes | |
if (argc < 4) | |
{ | |
printf("Uso: huffman [OPCAO] [ARQUIVO] [ARQUIVO]\n\n"); | |
printf("Opcoes:\n"); | |
printf("\t-c\tComprime\n"); | |
printf("\t-x\tDescomprime\n"); | |
printf("\nExemplo: ./huffman -c comprima.isso nisso.hx\n"); | |
return 0; | |
} | |
if (strcmp("-c", argv[1]) == 0) | |
{ | |
if (strstr(argv[3], ".hx")) | |
{ | |
CompressFile(argv[2], argv[3]); | |
} | |
else | |
{ | |
printf("O arquivo resultante da compressao deve ter a extensao '.hx'.\n"); | |
printf("Exemplo: \n\t./huffman -c comprima.isso nisso.hx\n"); | |
return 0; | |
} | |
} | |
else if (strcmp("-x", argv[1]) == 0) | |
{ | |
if (strstr(argv[2], ".hx")) | |
{ | |
DecompressFile(argv[2], argv[3]); | |
} | |
else | |
{ | |
printf("O arquivo a ser descomprimido deve ter a extensao '.hx'.\n"); | |
printf("Exemplo: \n\t./huffman -x descomprime.hx nisso.extensao\n"); | |
return 0; | |
} | |
} | |
else | |
{ | |
printf("Uso: huffman [OPCAO] [ARQUIVO] [ARQUIVO]\n\n"); | |
printf("Opcoes:\n"); | |
printf("\t-c\tComprime\n"); | |
printf("\t-x\tDescomprime\n"); | |
printf("\nExemplo: ./huffman -c comprima.isso nisso.hx\n"); | |
return 0; | |
} | |
//CompressFile("meslo.ttf", "meslo.hx"); | |
//DecompressFile("meslo.hx", "meslo_copy.ttf"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment