Está en la página 1de 22

1.- Objetivo.

Bueno este proyecto está basado en un lenguaje de programación que en la


línea de código dejaremos el código en la documentación por lo cual la línea de
código este hecho en “C”
Por lo cual en esta presentación haremos una explicación breve de cómo será
de manera explicada de tal manera que este proyecto que vamos a presentar
sea para que sea de uso en la compresión y descompresión de ficheros en el
cual ya se especificó el lenguaje de programación en el cual se hará la
codificación

2.- Fundamento Teórico

Codificación Hoffman: En ciencias de la computación y teoría de la información,


la codificación Huffman es un algoritmo usado para compresión de datos. El
término se refiere al uso de una tabla de códigos de longitud variable para
codificar un determinado símbolo (como puede ser un carácter en un archivo),
donde la tabla ha sido rellenada de una manera específica basándose en la
probabilidad estimada de aparición de cada posible valor de dicho símbolo. Fue
desarrollado por David A. Hoffman mientras era estudiante de doctorado en el
MIT, y publicado en "A Method for the Construction of Minimum-Redundancy
Codes".

La codificación Huffman usa un método específico para elegir la representación


de cada símbolo, que da lugar a un código prefijo (es decir, la cadena de bits
que representa a un símbolo en particular nunca es prefijo de la cadena de bits
de un símbolo distinto) que representa los caracteres más comunes usando las
cadenas de bits más cortas, y viceversa. Huffman fue capaz de diseñar el
método de compresión más eficiente de este tipo: ninguna representación
alternativa de un conjunto de símbolos de entrada produce una salida media
más pequeña cuando las frecuencias de los símbolos coinciden con las usadas
para crear el código. Posteriormente se encontró un método para llevar esto a
cabo en un tiempo lineal si las probabilidades de los símbolos de entrada
(también conocidas como "pesos") están ordenadas.

Para un grupo de símbolos con una distribución de probabilidad uniforme y un


número de miembros que es potencia de dos, la codificación Huffman es
equivalente a una codificación en bloque binaria, por ejemplo, la codificación
ASCII. La codificación Huffman es un método para crear códigos prefijo tan
extendido que el término "codificación Huffman" es ampliamente usado como
sinónimo de "código prefijo", incluso cuando dicho código no se ha producido
con el algoritmo de Huffman.

Aunque la codificación de Huffman es óptima para una codificación símbolo a


símbolo dada una distribución de probabilidad, su optimalidad a veces puede
verse accidentalmente exagerada. Por ejemplo, la codificación aritmética y la
codificación LZW normalmente ofrecen mayor capacidad de compresión. Estos
dos métodos pueden agrupar un número arbitrario de símbolos para una
codificación más eficiente, y en general se adaptan a las estadísticas de
entrada reales. Este último es útil cuando las probabilidades no se conocen de
forma precisa o varían significativamente dentro del flujo de datos.

Descripción formal
Entradas

El alfabeto , que es el alfabeto de símbolos de tamaño .

El conjunto , que es el conjunto de pesos (positivos) de los símbolos

(normalmente proporcionales a probabilidades), es decir .

Salida

El código , que es el conjunto de elementos del código (binario), donde

es la palabra del código para .

Objetivo

Sea la longitud del camino ponderado del código . Condición:

para cualquier código .

Ejemplo
Entrada (A, Símbolo (ai) a b c d e Suma
W) Peso (wi) 0.10 0.15 0.30 0.16 0.29 =1

Palabras del código


010 011 11 00 10
(ci)

Longitud de la palabra
(en bits) 3 3 2 2 2
Salida C
(li)

Longitud del camino


L(C) =
ponderado 0.30 0.45 0.60 0.32 0.58
2.25
(li wi )

Probabilidad
Optimalidad 1/8 1/8 1/4 1/4 1/4 = 1.00
(2-li)
Cantidad de
información (en bits) 3.32 2.74 1.74 2.64 1.79
(−log2 wi) ≈

Entropía H(A) =
0.332 0.411 0.521 0.423 0.518
(−wi log2 wi) 2.205

Para cualquier código biunívoco, aquel código decodificable de forma única, la


suma de las probabilidades de todos los símbolos es siempre menor o igual
que uno. En este ejemplo, es exactamente igual a uno; por lo que decimos que
es un código completo. Si no es el caso siempre se puede derivar un código
equivalente añadiendo símbolos extra (con probabilidades nulas asociadas),
para hacer el código completo a la vez que se mantiene biunívoco.

Tal como definió Shannon (1948), la cantidad de información h (en bits) de


cada símbolo ai con probabilidad no nula es

La entropía H (en bits) es la suma ponderada, de todos los símbolos ai con


probabilidad no nula wi, de la cantidad de información de cada símbolo:

(Nota: un símbolo con probabilidad cero tiene una contribución nula a la

entropía. Cuando w = 0, es una indeterminación; aplicando la regla de


L'Hôpital :

Por simplicidad, los símbolos con probabilidad nula han sido dejados fuera de
la fórmula anterior).

Como consecuencia del teorema de codificación de fuente de Shannon, la


entropía es una medida de la longitud de palabra más pequeña del código que
es teóricamente posible para un alfabeto dado con unos pesos asociados. En
este ejemplo, la longitud media de la palabra es 2,25 bits por símbolo,
ligeramente mayor que la entropía calculada de 2,205 bits por símbolo. Así que
no sólo este código es óptimo en el sentido de que ningún otro código posible
funciona mejor, sino que además está muy cercano al límite teórico establecido
por Shannon.

Lenguaje de programación “C”


C es un lenguaje de programación originalmente desarrollado por Dennis
Ritchie entre 1969 y 1972 en los Laboratorios Bell,2 como evolución del anterior
lenguaje B, a su vez basado en BCPL.

Al igual que B, es un lenguaje orientado a la implementación de Sistemas


operativos, concretamente Unix. C es apreciado por la eficiencia del código que
produce y es el lenguaje de programación más popular para crear software de
sistemas, aunque también se utiliza para crear aplicaciones.

Se trata de un lenguaje de tipos de datos estáticos, débilmente tipificado, de


medio nivel, ya que dispone de las estructuras típicas de los lenguajes de alto
nivel pero, a su vez, dispone de construcciones del lenguaje que permiten un
control a muy bajo nivel. Los compiladores suelen ofrecer extensiones al
lenguaje que posibilitan mezclar código en ensamblador con código C o
acceder directamente a memoria o dispositivos periféricos.

La primera estandarización del lenguaje C fue en ANSI, con el estándar


X3.159-1989. El lenguaje que define este estándar fue conocido vulgarmente
como ANSI C. Posteriormente, en 1990, fue ratificado como estándar ISO
(ISO/IEC 9899:1990). La adopción de este estándar es muy amplia por lo que,
si los programas creados lo siguen, el código es portable entre plataformas y/o
arquitecturas.

Proceso de compilación

La compilación de un programa C se realiza en varias fases que normalmente


son automatizadas y ocultadas por los entornos de desarrollo:

1. Preprocesado consistente en modificar el código fuente en C según una


serie de instrucciones (denominadas directivas de preprocesado)
simplificando de esta forma el trabajo del compilador. Por ejemplo, una
de las acciones más importantes es la modificación de las inclusiones
(#include) por las declaraciones reales existentes en el archivo indicado.
2. Compilación que genera el código objeto a partir del código ya
preprocesado.
3. Enlazado que une los códigos objeto de los distintos módulos y
bibliotecas externas (como las bibliotecas del sistema) para generar el
programa ejecutable final.

Ejemplo de código

El siguiente programa imprime en pantalla la frase "Hola Mundo" (C99).

// necesario para utilizar printf()


# include <stdio.h>

int main(void) {
printf("Hola Mundo\n");
return 0;
}
El siguiente escribe "Hola Mundo" en C89

/* comentarios con '//' no permitidos en C89, sí en C99 */


# include <stdio.h> /* necesario para utilizar printf */

main() /* tipo 'int' de retorno implícito */


{
printf ("Hola Mundo\n") ;
return 0;
}

Estructura de control "else if"

if (condicion 1) {
sentencia 1
} else if (condicion 2){
sentencia 2
} else if (condicion n){
sentencia n
} else {
sentencias por defecto
}

3.- Diseño e implementación.-

Bueno este proyecto fue diseñado e implementado en el lenguaje de


programación C por la cual se diseñó un código el cual pueda comprimir y
descomprimir un fichero el cual será un pequeño párrafo del cual
seleccionaremos de un articulo del periódico “LA PATRIA” cuyo párrafo tiene
que tener un mínimo entre 50 palabras a 100 considerando el espacio de
separado de palabras

El cual también va determinar la frecuencia relativa de cada palabra que se va


poder ver dentro del párrafo dentro del artículo que seleccionaremos´

Cuyo artículo vamos a selecciona y dejaremos en anexos las distintas capturas


de pantalla para verificación de código y el código

Por lo cual a continuación dejaremos el código con sus respectivas


explicaciones de cada parte y acciones que hace cada parte para su mejor
comprensión
3.1.- Código

#include <stdio.h>

#include <malloc.h>

struct nodo

struct nodo *der,*izq,*arr; /* forma el nodo */

int cuenta; /* apariciones del caracter */

char bit; /* 0 o 1 */

unsigned char carácter; /* el caracter (para la descompresion */

char *codigo; /* cadena de ceros y unos con la codificacion */

char nbits; /* me apunto el numero de bits que codifican el


caracter */

}HOJAS[256],*TELAR[256],*MENOR,*SEGUNDO;

int NSIMB=0,nsimb;

FILE *f,*g;

int NBYTES=0;

/*--------------------------------

preparar las hojas

--------------------------------*/

int preparar_hojas(char *archivo)

int j;
for(j=0;j<256;++j){

HOJAS[j].der=HOJAS[j].izq=HOJAS[j].arr=NULL;

HOJAS[j].cuenta=0;

HOJAS[j].karacter=j;

HOJAS[j].código=NULL;

if ((f=fopen(archivo,"rb"))!=NULL){

while ((j=fgetc(f))!=EOF){

++HOJAS[j].cuenta;

++NBYTES;

fclose(f);

else

return(1);

for(j=0;j<256;++j){

if (HOJAS[j].cuenta!=0)

++NSIMB;

nsimb=NSIMB;

return(0);

}
/*--------------------------------

preparar telar

--------------------------------*/

void preparar_telar()

int j;

for(j=0;j<256;++j){

TELAR[j]=&(HOJAS[j]);

return;

/*--------------------------------

tejer el árbol

--------------------------------*/

void tejer()

int menor=-1; /* guarda indice */

int segundo=-1; /* guarda indice */

int temporal; /* guarda cuenta */

int j;

struct nodo *P; /* nuevo nodo */

if (nsimb==1) return;
/* buscar menor valor */

for(j=0;j<256;++j){

if (TELAR[j]==NULL) continue;

if (TELAR[j]->cuenta==0) continue;

if (menor==-1){

menor=j;

temporal=TELAR[j]->cuenta;

} else

if (TELAR[j]->cuenta<temporal){

menor=j;

temporal=TELAR[j]->cuenta;

/* buscar segundo menor */

for(j=0;j<256;++j){

if (TELAR[j]==NULL) continue;

if (TELAR[j]->cuenta==0) continue;

if (j==menor) continue;

if (segundo==-1){

segundo=j;

temporal=TELAR[j]->cuenta;

} else
{

if (TELAR[j]->cuenta<temporal){

segundo=j;

temporal=TELAR[j]->cuenta;

/* tejer un nuevo nodo */

P=(struct nodo *)malloc(sizeof(struct nodo));

TELAR[menor]->arr=P;

TELAR[segundo]->arr=P;

P->izq=TELAR[menor];

P->der=TELAR[segundo];

P->arr=NULL;

TELAR[menor]->bit=0;

TELAR[segundo]->bit=1;

P->cuenta=TELAR[menor]->cuenta+TELAR[segundo]->cuenta;

TELAR[menor]=NULL;

TELAR[segundo]=P;

--nsimb;

/* sigue tejiendo hasta que solo quede un nodo */

tejer();

}
/*--------------------------------

Una vez construido el Arbol, puedo codificar

cada caracter. Para eso recorro desde la hoja

a la rai-z, apunto 0 o 1 en una pila y luego

paso la pila a una cadena. Un 2 determina el

fin de la cadena.

--------------------------------*/

void codificar()

char pila[64];

char tope;

int j;

char *w;

struct nodo *P;

for(j=0;j<256;++j){

if (HOJAS[j].cuenta==0) continue;

P=(struct nodo *)(&(HOJAS[j]));

tope=0;

while (P->arr!=NULL){

pila[tope]=P->bit;

++tope;

P=P->arr;

HOJAS[j].nbits=tope;
HOJAS[j].código=(char *)malloc((tope+1)*sizeof(char));

w=HOJAS[j].código;

--tope;

while (tope>-1){

*w=pila[tope];

--tope;

++w;

*w=2;

return;

/*--------------------------------

debug. Imprime la info sobre cada

caracter, como numero de apariciones

y cadena con que se codifica

--------------------------------*/

void debug()

int j,k;

char *w;

int tam_comprimido=0;

for(j=0;j<256;++j){
if (HOJAS[j].cuenta==0) continue;

tam_comprimido+=(HOJAS[j].cuenta*HOJAS[j].nbits);

printf("%3d %6d ",j,HOJAS[j].cuenta);

w=HOJAS[j].código;

while (*w!=2){

printf("%c",48+(*w));

++w;

printf("\n");

printf("NSIMB: %d\n",NSIMB);

printf("NBYTES: %d\n",NBYTES);

printf("TAMAÑO COMPRIMIDO: %d\n",tam_comprimido/8+1);

return;

/*--------------------------------

Escribe la cabecera del archivo de

destino. La cabecera contiene: el

numero de bytes del archivo origen,

el numero de caracteres distintos

en ese archivo y una lista de parejas

numero de caracter-cuenta de ese

caracter. Eso es suficiente para la

descompresion
--------------------------------*/

int escribe_cabecera(char *destino)

int j,k;

FILE *g;

char *p=(char *)(&NBYTES);

if ((g=fopen(destino,"wb"))==NULL) return(1);

for(j=0;j<4;++j){

fputc(*p,g);

++p;

p=(char *)(&NSIMB);

fputc(*p,g);

for(j=0;j<256;++j){

if (HOJAS[j].cuenta==0) continue;

fputc(j,g);

p=(char *)(&(HOJAS[j].cuenta));

for(k=0;k<4;++k){

fputc(*p,g);

++p;

}
fclose(g);

return(0);

/*--------------------------------

Una vez construido el árbol y codificado

cada caracter se puede proceder a la

compresion: se tomara caracter a caracter

del archivo origen y se usara la cadena

de codificacion para ir escribiendo

bits en un buffer de un caracter, que

cada vez que quede lleno se pasara al

archivo de destino

--------------------------------*/

int comprimir(char *origen, char *destino)

unsigned char d=0;

int x;

char nbit=0;

char *p;

if ((f=fopen(origen,"rb"))==NULL) return(1);

if ((g=fopen(destino,"ab"))==NULL) return(2); /* ya esta la cabecera */

while ((x=fgetc(f))!=EOF){
p=HOJAS[x].código;

while (*p!=2){

if (nbit==8){

nbit=0;

fputc(d,g);

d=0;

} else

if (*p==1){

d|=(1<<nbit);

++nbit;

++p;

fputc(d,g);

fclose(f);

fclose(g);

return(0);

/*--------------------------------

Descomprime el archivo. El primer paso

es leer la cabecera, paso previo a la


descompresion. Recuerdo formato de

la cabecera:

NBYTES|NSIMB|(char,cuenta)*

--------------------------------*/

int descomprimir(char *origen, char *destino)

char *p;

int j,k,n,m;

unsigned char x,nbit;

struct nodo *P,*Q;

if ((g=fopen(origen,"rb"))==NULL) return(1);

if ((f=fopen(destino,"wb"))==NULL) return(2);

/* leer NBYTES */

p=(char *)(&n);

for(j=0;j<4;++j){

*p=(unsigned char)fgetc(g);

++p;

NBYTES=n;

/* leer NSIMB */

NSIMB=nsimb=fgetc(g);
/* preparar las hojas */

for(j=0;j<256;++j){

HOJAS[j].cuenta=0;

HOJAS[j].izq=HOJAS[j].der=HOJAS[j].arr=NULL;

HOJAS[j].karacter=j;

for(j=0;j<NSIMB;++j){

n=fgetc(g);

p=(char *)(&m);

for(k=0;k<4;++k){

*p=(unsigned char)fgetc(g);

++p;

HOJAS[n].cuenta=m;

/* construyo el árbol */

preparar_telar();

tejer();

/* apunto a la rai-z del Arbol */

j=0;

while (HOJAS[j].cuenta==0) ++j;

P=(struct nodo *)(&(HOJAS[j]));

while (P->arr!=NULL) P=P->arr;


/* ahora ya se puede descomprimir */

j=0;

x=fgetc(g);

nbit=0;

Q=P;

while(j<NBYTES){

if (Q->izq==NULL){

fputc(Q->karacter,f);

Q=P;

++j;

} else

if (nbit==8){

x=fgetc(g);

nbit=0;

} else

if (x&(1<<nbit)){

Q=Q->der;

else

Q=Q->izq;

++nbit;
}

fclose(f);

fclose(g);

return(0);

main(int argc, char *argv[])

{ int j;

if (argc<2) return;

if (*(argv[1])=='C'){ /* comprimir */

if (argc!=4) return;

if (preparar_hojas(argv[2])){

printf("error abriendo archivo\n");

return;

preparar_telar();

tejer();

codificar();

if (escribe_cabecera(argv[3])){

printf("error abriendo archivo\n");

return;

if (comprimir(argv[2],argv[3])){

printf("error abriendo archivo\n");


return;

else

if (*(argv[1])=='D'){ /* descomprimir */

if (argc!=4) return;

if (descomprimir(argv[2],argv[3])){

printf("error abriendo archivo\n");

return;

else

if (*(argv[1])=='I'){ /* info */

if (argc!=3) return;

if (preparar_hojas(argv[2])){

printf("error abriendo archivo\n");

return;

preparar_telar();

tejer();

codificar();

debug();

return;

}
4. Conclusiones

Bueno al acabar la presentación y todo el problema y demás parte que se dio a


conocer para el cual se llega a la conclusión de que la codificación efectuada y
realizada pues tiene un funcionamiento factible y de manera que puede cumplir
con las expectativas de nuestro requeriemiento el cual podemos decir que a
cabalidad de todo esto también dejaremos en un archivo txt nuestro código
para que se pueda de manera rápida llevar a un compilador del lenguaje de
programación para que se vea el funcionamento
Parte de la implementación del código

También podría gustarte