1. Tcnicas de diccionario
1.1. Generalidades
1.2. Introduccin
Ejemplo:
Suponga que tenemos un texto particular que consiste en palabras de cuatro
caracteres. Tres caracteres de las 26 letras minsculas del alfabeto ingls seguidas de una
marca de puntuacin (coma, punto, exclamacin, interrogacin, punto y coma y dos
puntos). En otras palabras, el tamao del alfabeto de entrada es 32. Si furamos a
codificar el texto fuente un carcter al tiempo, tratando cada carcter como un evento
igualmente probable, necesitaramos . Tratando todos los 324 =
1048576 patrones de cuatro caracteres como igualmente probables, tendramos un
cdigo que asignara 20 a cada patrn de cuatro caracteres.
Pongamos ahora los 256 patrones de cuatro caracteres ms probables en un
diccionario. El esquema de transmisin funciona como sigue: siempre que deseemos
enviar un patrn que exista en el diccionario enviaremos un bit bandera, digamos un 0,
seguido por un ndice de correspondiente a la entrada en el diccionario. Si el patrn
no se encuentra en el diccionario enviaremos un 1 seguido de que codifican el
patrn. Si el patrn que encontramos no est en el diccionario, nosotros enviaremos
realmente ms bits que en el esquema original, en vez de . Pero si est en el
diccionario enviaremos solo . La utilidad de nuestro esquema depender del
porcentaje de palabras que encontramos que est en el diccionario. Podemos tener una
idea acerca de la utilidad de nuestro esquema calculando el nmero promedio de bits por
patrn.
Si la probabilidad de encontrar un patrn del diccionario es , entonces el nmero
promedio de / esta dado por:
= + ( ) =
2
Para que nuestro esquema sea til, debera tener un valor inferior a . Esto sucede
cuando . . Esto no parece como un nmero muy grande. Sin embargo observe
que si todos los patrones ocurrieran en una manera igualmente probable, la probabilidad
de encontrar un patrn del diccionario sera menor que .
De acuerdo con este ejemplo si se quiere mejorar mucho el desempeo se debe aumentar
la probabilidad de que la secuencia est en el diccionario tanto como sea posible, o sea
que las entradas del diccionario deben ser cuidadosamente escogidas. Para lograr esto
se debe tener una muy buena idea del comportamiento de la fuente o de lo contrario
debemos estar en capacidad de obtener esta informacin de alguna manera.
Dependiendo de si conocemos previamente el comportamiento de la fuente o no, hay dos
maneras de construir el diccionario: si se conoce dicho comportamiento se puede
construir un diccionario esttico y de lo contrario se pude construir un diccionario
adaptativo.
1.3. Diccionario esttico
Construir un diccionario esttico es ms apropiado cuando se tiene un conocimiento a
priori del comportamiento de la fuente. Este mtodo es especialmente apropiado de usar
en aplicaciones especficas.
Ejemplo:
Compresin de los datos de estudiantes de la universidad.
Se tiene la certeza de que datos como el nombre, la identidad, etc. aparecern en todos
las fichas. Dependiendo de la ubicacin geogrfica ciertos dgitos de la tarjeta de
seguridad social son ms probables.
Los diccionarios generados para esta aplicacin no trabajaran bien en otros casos y por el
contrario podran causar una expansin en vez de una compresin.
1.3.1. Codificacin digrama
Es una tcnica que es menos especfica a una aplicacin. Esta tcnica consiste en un
diccionario formado por todas las letras del alfabeto de la fuente seguida de tantos pares
de letras (digramas) como sea posible de acomodar en el diccionario.
Ejemplo:
Construccin de un diccionario de de todos los caracteres
imprimibles. Las primeras son los caracteres imprimibles y el resto
seria los ms frecuentes.
El codificador digrama lee dos caracteres de entrada y busca el diccionario para ver si
existe la entrada en el diccionario. Si lo es se codifica el ndice correspondiente y se enva,
si no est se codifica el primer carcter del par. El segundo carcter se convierte en el
primer carcter del siguiente digrama. El codificador lee el siguiente carcter para
completar un digrama y se repite el procedimiento.
3
Ejemplo:
Supongamos que tenemos una fuente con un alfabeto de cinco letras = {, , , } ,
basados en el conocimiento que tenemos acerca de la fuente, construimos el diccionario
mostrado en la tabla 1:
Cdigo Entrada Cdigo Entrada
Conteo
Par Conteo
4
):
.
5
Esto se debe a que la longitud del acople puede ser superior a la ventana de bsqueda.
Ejemplo:
Suponga que se quiere codificar la secuencia
Suponga adems que la longitud de la ventana es , el tamao del buffer de bsqueda
es y que la condicin corriente es como sigue:
Para codificar la , se puede ver que no hay acople y por lo tanto se transmite la tripleta
, , ()
ndice Entrada
8
El cuarto smbolo es una b, la cual es la tercer entrada en el diccionario. Si le aadimos
el siguiente smbolo, obtenemos el patrn el cual no est en el diccionario, luego
codificamos estos dos smbolos como , , y agregamos el patrn como la cuarta
entrada en el diccionario.
Diccionario
Salida del codificador ndice Entrada
, ()
, ()
, ()
, ()
, ()
9
ndice Entrada
10
11
12
10
diccionario al final del proceso de codificacin se muestra en la tabla 8. Observe que la
todas las entradas de la doce a la tienen una longitud de o de cuatro letras. Luego
encontramos el patrn por primera vez y caemos a un patrn de dos letras por tres
entradas ms, despus de lo cual regresamos a entradas de longitud en incremento.
La secuencia de salida del codificador es
ndice Entrada ndice Entrada
11
ndice Entrada
12
Un algoritmo en :
/**********************************************************************
** Copyright (c) 1989 Mark R. Nelson
**
** LZW data compression/expansion demonstration program.
**
13
** April 13, 1989
**
** Minor mods made 7/19/2006 to conform with ANSI-C - prototypes, casting,
** and argument agreement.
**********************************************************************
*********/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#define BITS 12
*/
*/
#if BITS == 14
#define TABLE_SIZE 18041
#endif
#if BITS == 13
*/
*/
14
unsigned int *prefix_code;
15
/*
** The three buffers are needed for the compression phase.
*/
code_value=(int*)malloc(TABLE_SIZE*sizeof(int));
prefix_code=(unsigned int *)malloc(TABLE_SIZE*sizeof(unsigned int));
append_character=(unsigned char *)malloc(TABLE_SIZE*sizeof(unsigned char));
if (code_value==NULL || prefix_code==NULL || append_character==NULL)
{
printf("Fatal error allocating table space!\n");
getch();
exit(-1);
}
/*
** Get the file name, open it up, and open up the lzw output file.
*/
if (argc>1)
strcpy(input_file_name,argv[1]);
else
{
gotoxy(10,10);
printf("Por favor ingrese el nombre del archivo: ");
scanf("%s",input_file_name);
}
input_file=fopen(input_file_name,"rb");
lzw_file=fopen("comp.txt","wb");
16
if (input_file==NULL || lzw_file==NULL)
{
printf("Fatal error opening files.\n");
getch();
exit(-1);
};
/*
** Compress the file.
*/
compress(input_file,lzw_file);
fclose(input_file);
fclose(lzw_file);
free(code_value);
/*
** Now open the files for the expansion.
*/
lzw_file=fopen("comp.txt","rb");
output_file=fopen("out.txt","wb");
if (lzw_file==NULL || output_file==NULL)
{
printf("Fatal error opening files.\n");
getch();
exit(-2);
};
/*
17
** Expand the file.
*/
expand(lzw_file,output_file);
fclose(lzw_file);
fclose(output_file);
free(prefix_code);
free(append_character);
getch();
}
/*
** This is the compression routine. The code should be a fairly close
** match to the algorithm accompanying the article.
**
*/
void compress(FILE *input,FILE *output)
{
unsigned int next_code;
unsigned int character;
unsigned int string_code;
unsigned int index;
int i;
next_code=256;
18
gotoxy(10,13);
printf("Comprimiendo...\n");
getch();
string_code=getc(input);
*/
/*
** This is the main loop where it all happens. This loop runs util all of
** the input has been exhausted. Note that it stops adding codes to the
** table after all of the possible codes have been defined.
*/
while ((character=getc(input)) != (unsigned)EOF)
{
if (++i==1000)
{
*/
/* is just a pacifier.
*/
printf("*");
}
index=find_match(string_code,character);/* See if the string is in */
if (code_value[index] != -1)
string_code=code_value[index];
else
{
19
append_character[index]=character;
}
output_code(output,string_code); /* When a string is found */
string_code=character;
}
}
/*
** End of the main loop.
*/
output_code(output,string_code); /* Output the last code
*/
printf("\n");
}
/*
** This is the hashing routine. It tries to find a match for the prefix+char
** string in the string table. If it finds it, the index is returned. If
** the string is not found, the first available index in the string table is
** returned instead.
*/
int find_match(int hash_prefix,unsigned int hash_character)
{
int index;
int offset;
index = (hash_character << HASHING_SHIFT) ^ hash_prefix;
*/
20
if (index == 0)
offset = 1;
else
offset = TABLE_SIZE - index;
while (1)
{
if (code_value[index] == -1)
return(index);
if (prefix_code[index] == hash_prefix &&
append_character[index] == hash_character)
return(index);
index -= offset;
if (index < 0)
index += TABLE_SIZE;
}
}
/*
** This is the expansion routine. It takes an LZW format file, and expands
** it to an output file. The code here should be a fairly close match to
** the algorithm in the accompanying article.
*/
void expand(FILE *input,FILE *output)
{
unsigned int next_code;
unsigned int new_code;
21
unsigned int old_code;
int character;
int counter;
unsigned char *string;
next_code=256;
counter=0;
*/
gotoxy(10,16);
printf("Descomprimiendo...\n");
old_code=input_code(input); /* Read in the first code, initialize the */
character=old_code;
putc(old_code,output);
*/
/*
** This is the main expansion loop. It reads in characters from the LZW file
** until it sees the special code used to inidicate the end of the data.
*/
while ((new_code=input_code(input)) != (MAX_VALUE))
{
if (++counter==1000) /* This section of code prints out
{
*/
/* It is just a pacifier.
*/
printf("*");
}
/*
** This code checks for the special
STRING+CHARACTER+STRING+CHARACTER+STRING
** case which generates an undefined code. It handles it by decoding
22
** the last code, and adding a single character to the end of the decode string.
*/
if (new_code>=next_code)
{
*decode_stack=character;
string=decode_string(decode_stack+1,old_code);
}
/*
** Otherwise we do a straight decode of the new code.
*/
else
string=decode_string(decode_stack,new_code);
/*
** Now we output the decoded string in reverse order.
*/
character=*string;
while (string >= decode_stack)
putc(*string--,output);
/*
** Finally, if possible, add a new code to the string table.
*/
if (next_code <= MAX_CODE)
{
prefix_code[next_code]=old_code;
append_character[next_code]=character;
23
next_code++;
}
old_code=new_code;
}
printf("\n");
}
/*
** This routine simply decodes a string from the string table, storing
** it in a buffer. The buffer can then be output in reverse order by
** the expansion program.
*/
unsigned char *decode_string(unsigned char *buffer,unsigned int code)
{
int i;
i=0;
while (code > 255)
{
*buffer++ = append_character[code];
code=prefix_code[code];
if (i++>=MAX_CODE)
{
printf("Fatal error during code expansion.\n");
exit(-3);
}
}
24
*buffer=code;
return(buffer);
}
/*
** The following two routines are used to output variable length
** codes. They are written strictly for clarity, and are not
** particularyl efficient.
*/
unsigned int input_code(FILE *input)
{
unsigned int return_value;
static int input_bit_count=0;
static unsigned long input_bit_buffer=0L;
while (input_bit_count <= 24)
{
input_bit_buffer |=
(unsigned long) getc(input) << (24-input_bit_count);
input_bit_count += 8;
}
return_value=input_bit_buffer >> (32-BITS);
input_bit_buffer <<= BITS;
input_bit_count -= BITS;
return(return_value);
}
void output_code(FILE *output,unsigned int code)
25
{
static int output_bit_count=0;
static unsigned long output_bit_buffer=0L;
output_bit_buffer |= (unsigned long) code << (32-BITS-output_bit_count);
output_bit_count += BITS;
while (output_bit_count >= 8)
{
putc(output_bit_buffer >> 24,output);
output_bit_buffer <<= 8;
output_bit_count -= 8;
}
}
Aplicaciones:
a. Compresor de archivos de
En este caso el tamao del diccionario es adaptativo y se arranca con . Una vez que
el diccionario se llena se dobla el tamao del diccionario de manera recursiva hasta que
se logra un tamao dado por siendo el tamao del cdigo de palabra que puede
estar entre .
Cuando se alcanza el tope del diccionario se vuelve una tcnica de
diccionario esttico. En este punto se monitorea la rata de compresin y si esta cae por
debajo de un umbral el diccionario es borrado y se recomienza la construccin.
b. Compresin de imgenes formato de intercambio de grficos ()
Fue desarrollado por la empresa para codificar
imgenes grficas.
La imagen comprimida se almacena con un primer byte que indica el mnimo nmero de
de la imagen original.
El nmero binario se define como el cdigo claro ( ) para resetear todos
los parmetros de compresin y descomprensin.
26
El tamao inicial del diccionario es . Cuando este se llena se dobla el tamao hasta
que se logra un tamao mximo de y luego de esto el algoritmo se comporta como
un algoritmo de diccionario esttico.
Las palabras de cdigo se almacenan en bloques de caracteres de cada uno y el
tamao mximo es de .
Cada bloque es precedido por un encabezado que contiene el tamao del bloque.
El bloque es terminado con un terminador de bloque que consiste de .
El fin de la imagen comprimida es denotado por un cdigo de fin de informacin con
valor 2 +1. Esta palabra de cdigo debe aparecer antes del terminador de bloque
Tierra
Omaha
27
c. Compresin sobre mdems .
Esta recomendacin est hecha para transmisin a travs de una red telefnica junto con
procedimientos de correccin de errores. Se usa en conexin de computadores con
usuarios remotos a travs de mdems. Tiene dos modos: modo transparente y modo
comprimido.
En el modo transparente se transmite sin comprimir en el caso de que no haya secuencias
repetitivas y por lo tanto se presente expansin en vez de compresin. Esto implica que
la recomendacin sugiera que se chequee peridicamente para observar si se da tal
situacin.
En el modo comprimido se usa un diccionario de tamao variable con un tamao inicial
que se negocia al momento de establecer el enlace entre el transmisor y el receptor.
La recomendacin . sugiere un tamao de y un mnimo de .
Se reservan tres entradas del diccionario para caracteres de control
Cdigo Nombre
Descripcin
Tabla 12: palabras de control en modo comprimido
Cuando el nmero de entradas supera un umbral pre acordado el codificador enva el
y el tamao de la palabra se incrementa en , al mismo tiempo se dobla el
valor de .
Cuando todas las entradas se llenan el algoritmo inicia un procedimiento de . La
localizacin de la primera entrada del diccionario se mantiene en una variable .
Arrancando de Un contador se incrementa hasta que el algoritmo encuentra una
entrada del diccionario que no es prefijo de cualquier otra entrada del diccionario, lo que
significa que este patrn no se ha vuelto a encontrar desde que se cre, adems debido a
la manera como fue localizado, entre patrones de esta clase este patrn ha estado alrededor
del ms largo. Este procedimiento de habilita al algoritmo para podar () el
diccionario de tiras que pueden haber sido encontradas en el pasado pero no han sido
encontradas recientemente en una base continua. De esta manera el diccionario se acopla
siempre a las estadsticas corrientes de la fuente.