/* Este programa implementa un sencillo compresor de Huffman, que podria usarse para un programa de mayor envergadura. comprimir: huf C entrada salida descompr.: huf D entrada salida info: huf I entrada */ #include #include struct nodo { struct nodo *der,*izq,*arr; /* forma el nodo */ int cuenta; /* apariciones del carácter */ char bit; /* 0 o 1 */ unsigned char karacter; /* el carácter (para la descompresión */ char *codigo; /* cadena de ceros y unos con la codificación */ char nbits; /* me apunto el numero de bits que codifican el carácter */ }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].codigo=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]->cuentacuenta; } } } /* 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]->cuentacuenta; } } } /* 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 sólo quede un nodo */ tejer(); } /*-------------------------------- Una vez construido el árbol, puedo codificar cada carácter. Para eso recorro desde la hoja a la raí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].codigo=(char *)malloc((tope+1)*sizeof(char)); w=HOJAS[j].codigo; --tope; while (tope>-1){ *w=pila[tope]; --tope; ++w; } *w=2; } return; } /*-------------------------------- debug. Imprime la info sobre cada carácter, como número 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].codigo; 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 número de bytes del archivo origen, el número de caracteres distintos en ese archivo y una lista de parejas número de carácter-cuenta de ese carácter. Eso es suficiente para la descompresión --------------------------------*/ 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 carácter se puede proceder a la compresión: se tomará carácter a carácter del archivo origen y se usará la cadena de codificación para ir escribiendo bits en un buffer de un carácter, que cada vez que quede lleno se pasará 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].codigo; while (*p!=2){ if (nbit==8){ nbit=0; fputc(d,g); d=0; } else { if (*p==1){ d|=(1<arr!=NULL) P=P->arr; /* ahora ya se puede descomprimir */ j=0; x=fgetc(g); nbit=0; Q=P; while(jizq==NULL){ fputc(Q->karacter,f); Q=P; ++j; } else if (nbit==8){ x=fgetc(g); nbit=0; } else { if (x&(1<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; }