LZW
# LZW

El algoritmo de Lempel Ziv y Welch, o abreviadamente como mejor se le conoce Algoritmo LZW, es un algoritmo de compresión sin pérdida basado en diccionario, desarrollado por Terry Welch en 1984 bastante tiempo después que fuera publicado el algoritmo LZ78 del que es una versión mejorada, que a su vez era una mejora sustancialmente distinta del algoritmo LZ77.

Si bien, a la luz de la aparición de los algoritmos de Zip y Lempel sobrevino un resurgimiento de la compresión, que permanecía atascada y limitada en los algoritmos estadísticos, este algoritmo abre el camino hacia una mayor eficiencia y velocidad.

## Estado previo del arte

La mayoría de los métodos de compresión basados en diccionario requieren dos etapas, una de análisis y una segunda de conversión.

La etapa de análisis inicial tiene por objeto identificar cadenas repetidas para armar el diccionario de equivalencias, asignando códigos breves a estas cadenas. En una segunda etapa, se convierte el texto utilizando los códigos equivalentes para las cadenas repetidas.

Dichos algoritmos también requieren que el diccionario se almacene junto con el texto codificado, incrementando el tamaño del archivo de salida.

El Algoritmo LZ77 desarrollado por Abraham Lempel y Jacob Ziv precisaba guardar 3 datos (Index, Count y Offset), para almacenar una secuencia. Las críticas constructivas vertidas sobre dicho algoritmo influencian la creación del Algoritmo LZ78, desarrollado también por los mismos autores, que solo precisa 2 datos para almacenar la referencia de la secuencia comprimida (count y Offset). El algoritmo LZW mejora los previos en dicho aspecto, pues solo precisa guardar como dato para referenciar la secuencia comprimida 1 dato (index), lo que a su vez simplifica y acelera toda la operativa.

## Descripción del algoritmo

La gran ventaja del algoritmo LZW es que permite crear sobre la marcha el diccionario de cadenas que se encuentren dentro del texto a comprimir y al mismo tiempo (sin requerir otra etapa) se procede a su codificación.

Además, dicho diccionario no precisa ser transmitido con el texto comprimido, puesto que el descompresor puede reconstruirlo, siguiendo el mismo procedimiento que hace el compresor. Si está codificado correctamente, tendrá exactamente las mismas cadenas que tenía el diccionario del compresor.

### El diccionario

El diccionario suele comenzar con un tamaño predefinido, y de entrada se precarga con las primeras 256 entradas, una para cada carácter (byte) posible, más un código predefinido usado como un indicador de final de fichero.

Cuando el diccionario se llena al comprimir, se debe elegir entre vaciar el diccionario y volver a precargarlo y empezar de nuevo, o dejar el diccionario fijo. La opción de aumentar el tamaño del diccionario es equivalente a si al principio se establece ese tamaño al que se vaya a aumentar. El compresor deberá emitir un código especial señalando la opción elegida si no está prefijada (no elegible). Originalmente en el diseño del algoritmo, el tamaño del diccionario venía determinado por la limitación de la memoria, era pues frecuente que en las primeras implementaciones del algoritmo el diccionario tuviera solo 4096 entradas (12 bits), que luego se fueron ampliando a 14, 15 y 16 bits (64kb). El tamaño del diccionario en las implementaciones recientes (a fecha de 2022) del algoritmo, es elegible por el usuario.

Durante la descompresión se sigue el mismo procedimiento que durante la compresión. El diccionario se precarga y cuando se ha llenado debe tomarse la misma acción que tomó el compresor. Si no está prefijada por convención en el algoritmo, al comprimir se debe emitir un código especial que el descompresor debe poder reconocer, para realizar exactamente la misma acción.

El algoritmo originalmente contempla que, cuando una secuencia fuera a forzar la ampliación del diccionario por encima del límite de bits prefijado, el diccionario se borre por completo, se inicialice nuevamente con los 256 códigos iniciales más el código de fin de archivo y se recomience el proceso.

Nótese que dado un límite teórico de códigos de n bits, un diccionario nunca podrá contener más de $2^n$ entradas. El diccionario se arma como una tabla donde el código que se emite como salida, es el índice y las cadenas que representa son las entradas de esta tabla. Adviértase que el código en sí no se almacena en la tabla sino que es el índice de la misma por lo que se calcula por la posición en la tabla.

### La entradas nuevas al diccionario

Otra característica importante del algoritmo es que los códigos en la salida se representan por cadenas de bits variables. El diccionario contiene inicialmente 257 códigos, 256 códigos para los 256 caracteres simples posibles con 8 bits y un código que representa el fin de archivo.

Como las 256 entradas precargadas exigen 8 bits para ser referidas por el índice donde se localizan, la siguiente entrada al diccionario comenzará a almacenar a fichero los índices con 9 bits (en el código puede usarse por comodidad enteros de 16 o 32 bits), lo que da para llenar el diccionario hasta la entrada 511. Al llegar a la posición 512, se precisará usar 10 bits, y siguiendo el mismo esquema con cada potencia de 2 (la entradas realizadas se duplican), se requerirá 1 bit más.

En la práctica, se verifica que las primeras entradas, correspondientes a códigos de 12 bits de longitud (4096 entradas) se llenan rápidamente por lo que es habitual comenzar el proceso no con códigos de 9 bits sino directamente con códigos de 12 bits.

Así cada vez que se duplica el tamaño del diccionario exige un bit más. No es preciso emitir un código especial de salida para reconocer la situación, basta simplemente considerarlo en el código para reconocer el caso, tanto en el compresor como en el descompresor, pues van sincronizados en cuanto a la lógica, lo que se conoce en el argot como lockstep. El modo adecuado de reconocer dicha situación es precisamente asignar un código especial, al comienzo será el valor 512, luego 1024, 2048, etc. comprobando con cada entrada que cuando se alcanza dicho valor se suma 1 a los bits que se tendrán que guardar.

A esta tabla se le van agregando sucesivos códigos numéricos por cada nuevo par de caracteres consecutivos que se lean que aún no constan en el diccionario.

Es en este detalle donde reside la brillantez del método: al armar el diccionario sobre la marcha se evita hacer dos pasadas sobre el texto, una analizando y la otra codificando y dado que la regla de armado del diccionario es tan simple, el descompresor puede reconstruirlo a partir del texto comprimido mientras lo lee, evitando así la necesidad de incluir el diccionario dentro del fichero comprimido. Se puede objetar que el diccionario contendrá códigos que no se utilizarán y por tanto contribuye a un tamaño grande del mismo, pero el objetivo es que el fichero comprimido sea pequeño aun cuando los procesos de compresión y descompresión pudieran ocupar mucha memoria con el diccionario.

Las entradas del diccionario pueden representar secuencias de caracteres simples o secuencias de códigos de tal forma que un código puede representar dos caracteres o puede representar secuencias de otros códigos previamente cargados que a su vez representen, cada uno de ellos, otros códigos o caracteres simples, o sea que un código puede representar desde uno a un número indeterminado de caracteres. En realidad, el algoritmo no discrimina entre códigos y caracteres simples pues el diccionario se carga inicialmente de códigos que representan los primeros 256 caracteres simples por lo que estos no son más que otros códigos dentro del mismo diccionario.

Todos los caracteres están inicialmente predefinidos en el diccionario así que siempre habrá al menos una coincidencia, sin embargo, lo que se busca es la secuencia más larga posible. Cada vez que se lee un nuevo carácter se revisa el diccionario para ver si forma parte de alguna entrada previa. Cuando el carácter leído no encuentre una secuencia más larga, entonces se emite la más larga que se hubiera encontrado y se agrega al diccionario una entrada formada por cualquiera que hubiera sido el código previo y este nuevo código. En tanto los caracteres sucesivos que se vayan leyendo ofrezcan más de una entrada posible en el diccionario, se siguen leyendo caracteres. Cuando la cadena sólo tiene una entrada en el diccionario, entonces se emite (guarda, escribe) el código correspondiente a esa entrada y se incorpora al diccionario una nueva entrada que representa el último código emitido y el nuevo.

Se ha comprobado empíricamente que la información en un archivo presenta 'regionalidad', o sea, que diferentes regiones de un mismo archivo presentan distintas regularidades, lo cual hace que el diccionario que se hubiera formado para una región de un archivo pueda no ser útil en otra región distinta.

Que el tamaño de los índices pueda ser incrementado de manera variable es una de las contribuciones de Welch. Otra de ellas fue especificar una estructura de datos eficiente para guardar el diccionario.

### Códigos especiales

Como se ha venido comentando, el diccionario puede incluir códigos especiales que se reservan una posición en el diccionario y que de incluirlos, forman parte de la inicialización del diccionario (como entradas 256 y 257).

Tales códigos son el de diccionario lleno y el de fin de fichero.

* Diccionario lleno: Indicará al descompresor que debe hacer la acción que se espera, típicamente borrar e inicializar el diccionario. Como se ha indicado más arriba, puede optarse al comprimir por dejar el diccionario fijo o alguna otra opción, de haber varias opciones, podría seguirle otro código (que no es interpretable para descomprimir), refiriendo la acción a tomar (típicamente valores: 0,1,2,3... que identifica la acción seguida por el compresor).
* Fin de fichero: Señala que se alcanzó el final de la descompresión. Si existe contenido detrás no pertenece al fichero a descomprimir. Típicamente puede ser un comentario que se acompaña al fichero comprimido.
* Aumento del tamaño del código en 1 bit más: Cuando las entradas en el diccionario han ocupado todas las posiciones que son direccionables con los bits actuales, puede emitirse un código especial para indicar al descompresor que haga lo mismo. Teniendo en cuenta que el descompresor recrea el diccionario en la misma medida, la emisión de este código no es estrictamente necesaria. Si bien es posible elegir empezar directamente con 12 bits u otro valor que el descompresor no tenga previsto, luego si un descompresor considera que siempre se empieza con 9 bits, podría recibir antes que nada varios de estos códigos para empezar a usar los bits que el compresor haya estimado como el valor inicial. Así mismo este valor inicial de bits podría ser consignado en la cabecera del fichero. Por lo que definitivamente no tiene una utilidad práctica si en el diseño del sistema se ha previsto la situación.

### Compresión

De forma resumida el algoritmo de compresión sigue estos pasos:

* Inicializar el diccionario con todas las entradas prefijadas. Bits = 9; EntradasPrefijadas = 257 (256 bytes + 1 código especial); LimiteDupSecuencias = 512; index = EntradasPrefijadas
* Hacer: Leer la entrada actual.
* Repetir mientras se localice en el diccionario la secuencia más larga que además contenga la entrada actual.
* Emitir a la salida el índice de la secuencia más larga localizada (es el dato a guardar con el actual tamaño de Bits).
* Añadir al diccionario (en posición index) la entrada actual apuntando al índice de la secuencia guardada. index +=1
* Si el diccionario está lleno, vaciar el diccionario y volver al paso 1.
* Si la ocupación del diccionario = LimiteDupSecuencias (duplica el tamaño) a partir de ahora los códigos que se emitan usarán un bit más. Bits +=1; LimiteDupSecuencias *=2
* Volver al paso 2 hasta fin de fichero
* Emitir el código especial de fin de fichero.

El diccionario comienza inicialmente añadiendo muchos códigos, porque las secuencias en ese punto son cortas, a medida que el diccionario crece, las secuencias van siendo cada vez más largas, lo que implica que el código emitido está comprimiendo más bytes (caracteres) que lo que sucedía al comienzo, si bien la cantidad de bits, incluso para índices bajos, es mayor (pues como se ha indicado anteriormente, se requiere un bit más cada vez que se duplica el tamaño ocupado del diccionario).

El algoritmo es tanto más eficaz cuanto más secuencias estén parcialmente repetidas y más largas sean. El ratio de compresión suele describir una curva a medida que el diccionario crece y mientras se mantenga la regionalidad vigente.

### Descompresión

De forma resumida el algoritmo de descompresión sigue estos pasos:

* Inicializar el diccionario con todas las entradas prefijadas. Bits = 9; EntradasPrefijadas = 257 (256 bytes + 1 código especial); LimiteDupSecuencias = 512; index = EntradasPrefijadas
* Leer el código actual comprimido (con el tamaño de bits actual).
* Si código actual = fin de fichero. Salir.
* Hacer: mientras (código actual > EntradasPrefijadas)
* Concatenar valor en la posición index del diccionario a la secuencia.
* código actual = el código al que apunta el código actual en la posición del diccionario
* Repetir
* Concatenar valor en la posición index del diccionario a la secuencia.
* Emitir a la salida la secuencia concatenada en orden inverso.
* Añadir al diccionario (en posición index) la entrada actual apuntando al índice de la secuencia guardada. index +=1; Vaciar secuencia (la secuencia concatenada).
* Si el diccionario está lleno, vaciar el diccionario y volver al paso 1.
* Si la ocupación del diccionario = LimiteDupSecuencias (duplica el tamaño) a partir de ahora los códigos que se emitan usarán un bit más. Bits +=1; LimiteDupSecuencias *=2
* Volver al paso 2 hasta fin de fichero

Durante la descompresión, el diccionario es inicializado con todas las secuencias de tamaño 1 (byte, carácter) y es reconstruido a medida que se van descomprimiendo las entradas, lo que hace innecesario tener que guardar o enviar el diccionario con los datos comprimidos.

El proceso de descompresión va leyendo de la entrada un código de cada vez, del tamaño de los bits que estén vigente. Como el diccionario está inicializado, siempre encuentra el primer y segundo códigos, que son usados para añadir una entrada nueva al diccionario. Más adelante las entradas o refieren a códigos precargados o a los códigos que se han ido añadiendo al diccionario durante la descompresión.

## Ejemplos

### Ejemplo sencillo

Dado que el algoritmo sirve para comprimir cualquier secuencia de bits, independientemente de si es texto o cualquier otro tipo de información, el ejemplo a continuación no ha sido traducido del original en inglés. En él se supone que los textos a comprimir se componen solamente de letras mayúsculas sin espacios, para lo cual bastan (en inglés) 26 códigos, del 1 al 26, para las mayúsculas más un código (en este caso se ha adoptado el cero, aunque en la práctica el 0 es un carácter válido) para representar el fin de archivo, que se ha representado gráficamente por el símbolo #. El texto a comprimir es:

y el proceso de compresión queda representado por la tabla siguiente. Para interpretarla, se sugiere ignorar la representación binaria, que se incluye simplemente para contabilizar el tamaño del archivo de salida. Los códigos del 1 al 26 se corresponden con caracteres simples 1 = A, 2 = B, ... 26 = Z y 27 = "fin de archivo". Del 28 en adelante cada código representa más de un carácter.

El texto original, compuesto de 25 caracteres que pueden representarse con 5 bits cada uno nos daría 125 bits. El resultado comprimido produce 5 códigos de 5 bits más 12 códigos de 6 bits, lo cual resulta en 97 bits, una reducción a menos del 78% del original. Nótese que cada carácter leído genera una nueva entrada en el diccionario, independientemente de si se utilizará o no. Esta simplicidad por parte del algoritmo de compresión permite que el descompresor pueda reconstruir el diccionario sin errores.

Cuando se comienza a utilizar 6 bits por código, todos los códigos se emiten con 6 bits, incluso los que originalmente sólo usaran 5 bits, completándose con ceros por izquierda.

### Ejemplo más detallado para comprimir

Se han dispuesto 6 columnas, se explica el significado de cada una:

       La columna Indice dic apunta a la siguiente entrada vacía en el diccionario y en la que se guardarán los datos.
       La columna Hash secuencia, contiene el hash que se ha calculado a la nueva secuencia que se forma con el hash de la secuencia previa y el carácter actual siendo tratado.
       La columna Emite a fichero contiene el código (se expone en decimal), que se guardará como valor de la compresión, con la cantidad de bits actuales. En el presente ejemplo, actualmente son 9 bits.
       La columna Guarda en dic contiene el valor del carácter actual que se añade y que por tanto extiende la secuencia, como se muestra en la columna Secuencia referida.
       La columna Secuencia referida, muestra la secuencia que se aloja en el diccionario bajo el índice que se muestra en su columna. Es decir a futuro podrá localizarse esa secuencia, que yace en el diccionario con el índice indicado, tomado de la tabla hash, en el índice que señala el hash.
       La columna Num Chars en secuencia, señala cuantos caracteres tiene actualmente la secuencia que es referida el diccionario por el índice. Esta columna se mantiene solo para reflejar cómo evoluciona el algoritmo.

Sea la cadena siguiente la que se pretende comprimir:

Sigamos el rastro para comprimir la última palabra 'trigal'.

   * El primer carácter es la 't', que se localiza en el índice 116 del diccionario (es una entrada precargada, se han omitido por razones de extensión y obviedad).

   * Tomamos de la entrada el siguiente carácter: 'r', ahora computamos el hash para la entrada formada por la secuencia 116 y 114 (los valores 't' + 'r'), nos arroja el hash 32528, consultamos la tabla hash en el índice 32528 y contiene el valor 257, que es el índice en el diccionario, si miramos dicha entrada (ver línea index 257), en efecto allí se localiza la secuencia 'tr', así como los índices 116 y 114 (secuencia previa y la que la extiende).

   * De nuevo tomamos otro carácter de la entrada: 'i', computamos el hash para 257 y 105 (valores en diccionario de 'tr' + 'i'), el hash calculado resulta ser el 24505, ahora miramos el contenido en la tabla hash en ese índice (24505) que contiene el índice del diccionario donde se localiza esa secuencia: 262. Podemos mirar dicha línea y ahí se verán todos estos datos.

   * Una vez más tomamos el siguiente carácter de la entrada: 'g', computamos su hash para los valores 262 y 103 (valores en el diccionario para la secuencia 'tri' y 'g', la secuencia encontrada y el actual carácter). Y nos ofrece el hash 01285, consultamos la tabla hash en dicho índice y esta vez resulta que está vacío. No existe, entonces tomamos el índice actual no ocupado en el diccionario que es la posición 288, en dicha posición se guardan los valores: 262 y 103, para que puedan ser buscados en lo sucesivo. En la tabla hash en el índice calculado 01285, guardamos el índice que referencia a la secuencia recién formada, 288, y a fichero se emite el código 262. Así este último código emitido de 9 bits, ha logrado comprimir 3 caracteres y ha extendido esa secuencia en 1 más. Aumentamos la entrada libre del diccionario en 1 (289).

   * Como la secuencia ha extendido la previa porque no existía en el diccionario, tomamos el carácter que ha extendido la secuencia anterior 'g', que es una entrada precargada y se localiza en la posición 103.

   * Ahora tomamos el siguiente carácter que es 'a', conmutamos el hash para 103 y 98 (valores en diccionario de 'g' + 'a'), el hash calculado resulta ser 22647, mirando en la tabla hash en ese índice, encontramos el valor 274, que es donde se localiza esa secuencia en el diccionario.

   * Tomamos el siguiente carácter 'l', computamos el hash para 274 y 108 (valores en diccionario de 'ga' + 'l'), que arroja el hash 55387, este hash no está ocupado en la tabla hash, entonces almacenamos en la tabla hash la posición libre actual del diccionario (289), que será donde se localice en lo sucesivo la secuencia 'gal' y la entrada del diccionario 289, apunta a la secuencia menor 'ga' posicionada en la posición 274 del diccionario.

   * Como hemos llegado al final del fichero, emitimos el código especial 256 con la cantidad de bits actuales, que son 9.

El fichero de salida, contendrá los valores que figuran en la columna Emite a fichero, pero cada código se habrá escrito con 9 bits. Por razones de simplicidad se obvia toda supuesta cabecera que suele acompañar a un fichero comprimido.

El texto a comprimir tiene 47 caracteres, pero solo ha sido preciso emitir (289-257)= 32 códigos + 1 código especial de fin de fichero. Si bien en este punto no se ahorran muchos bits, ya que cada código se emite ahora mismo con 9 bits y al estar el diccionario vacío, las secuencias que se empiezan a formar son cortas. Todavía al final ya puede verse alguna secuencias de 4 caracteres.

    La cadena original precisaba: (47*8)= 376 bits.
    La cadena ahora comprimida precisa: (33*9)= 297 bits.

## Usos

Este algoritmo ha influenciado en mayor o menor medida otros que han derivado de éste, así como muchos programas. Otro capítulo referente a la historia de dicho algoritmo es el asunto relativo a las patentes, descrito más ampliamente en una sección más abajo.

El método llegó a ser utilizado de forma moderada, pero en toda su amplitud en el programa compress que llegó a ser más o menos la utilidad estándar de compresión en sistemas Unix alrededor de 1986 (ahora ha desaparecido prácticamente tanto por asuntos legales como técnicos). Otras utilidades de compresión también utilizan este método u otros relativamente cercanos.

Se usó ampliamente desde que se convirtió en parte del formato gráfico GIF en 1987. Puede ser también usado, aunque opcionalmente, en archivos TIFF.

La compresión LZW proporcionaba una relación de compresión mejor en muchas aplicaciones que otros métodos de compresión conocidos en esa época. Llegó a convertirse en el primer método de propósito general de compresión de datos usado ampliamente. En textos largos, comprime aproximadamente a la mitad del tamaño original. Otros tipos de datos son también comprimidos útilmente en muchos casos.

## Patentes

Varias patentes han sido concedidas en Estados Unidos de América y otros países por el algoritmo LZW y similares. El LZ78 estaba bajo la patente 4,464,650, pedida por Lempel, Ziv, Cohn y Eastman y asignada a Sperry Corporation, más tarde Unisys Corporation, el 10 de agosto de 1981. Dos patentes de los Estados Unidos fueron creadas para el LZW: la patente de EE. UU. 4,814,746 por Victor S. Miller y Mark N. Wegman y asignada a IBM, originalmente el 1 de junio de 1983, y la patente estadounidense 4,558,302 por Welch, asignada a Sperry Corporation, más tarde Unisys Corporation, el 20 de junio de 1983.

La patente estadounidense 4,558,302 es la que ha causado la mayor controversia. Unisys una vez garantizó licencias libre de patentes a desarrolladores de software libre y software propietario freeware (gratuito, sin fines comerciales). La compañía finalizó esas licencias en agosto de 1999.

Muchos expertos en leyes concluyen que la patente no cubre dispositivos que sólo descompriman LZW y no puedan comprimir datos usándolo, por esta razón el popular programa Gzip puede leer archivos .Z pero no escribirlos.

Se informó en Debian Weekly News basándose en un hilo de comp.compression, que la patente de Unisys en EE. UU. expiró en diciembre de 2002 - 17 años y 10 días después de ser patentado. Sin embargo la mayoría de las fuentes informan que expiró en junio de 2003, 20 años después de que fuera archivada, porque 35 USC §154(c)(1) especifica que las patentes subsisten 20 años después del Uruguay Round Agreements Act.

De acuerdo con una declaración en la web de Unisys, las patentes de LZW en el Reino Unido, Francia, Alemania, Italia y Japón han expirado en junio de 2004 y la patente canadiense en julio de 2004.

La patente de IBM en EE. UU. expiró en agosto de 2006.

