For this first part I implemented a binary tree to find the codes for each symbol.
1. The first thing that I did was to create a frequency table.
2. Then I put in pairs the lighter weights until the end of the list.
3. I searched the tree to find the codes for each leaf
I have done a web application to display the Huffman coding in a binary tree, this is the URL:
http://huffmandemo.appspot.com/
And here is the example:
After the decoding algorithm, I did take the text file that I wrote in binary, and I found the equivalent to the tree that I had already done.
This is the example in the terminal encoding and decoding:
Analyze an experiment to determine the worst-case and typical-case complexity and compression ratio
I use texts of 3000 words, for the worst case I have done texts with the same frequency and for the typical case I take random Internet texts
Here I show some plots that I got when I did the experiment:http://huffmandemo.appspot.com/
And here is the example:
After the decoding algorithm, I did take the text file that I wrote in binary, and I found the equivalent to the tree that I had already done.
This is the example in the terminal encoding and decoding:
Analyze an experiment to determine the worst-case and typical-case complexity and compression ratio
I use texts of 3000 words, for the worst case I have done texts with the same frequency and for the typical case I take random Internet texts
![]() |
compression ratio |
The complexity of the algorithm is O(nlog n). Since it has an internal loop with logn complexity and then repeated n times, where n is the set of symbols.
And this is the code:
This file contains 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
#!/usr/bin/python | |
DEBUG = True | |
#DEBUG = False | |
import string | |
import time | |
import random | |
import matplotlib.pyplot as plt | |
def genera_largos(): | |
""" | |
""" | |
largos = [] | |
for i in range(1,40): | |
largos.append(i*80) | |
return largos | |
def genera_peor_caso(largos): | |
""" | |
""" | |
caracteres = string.printable[:80] | |
dic= {} | |
cadena = '' | |
for i in range(len(largos)): | |
cadena = cadena + caracteres | |
dic[largos[i]] = cadena | |
return dic | |
def probabilidad_tipica(largos): | |
f = open("texto.txt") | |
texto = f.read() | |
texto = texto.replace("\n","") | |
dic = {} | |
total = len(texto) | |
for i in range(len(largos)): | |
inicio = random.randint(0, total/2) | |
cadena = texto[inicio:(inicio+largos[i])] | |
dic[largos[i]] = cadena | |
return dic | |
class Nodo: | |
"""nodo hoja | |
""" | |
def __init__(self, simbolo, peso): | |
self.simbolo = simbolo | |
self.peso = peso | |
self.der = None | |
self.izq = None | |
self.padre = None | |
self.x = 0 | |
self.y = 50 | |
self.ancho = 3000 | |
self.largo = None | |
def escribe_tabla(codigos): | |
"""escribe el diccionario en un archivo | |
""" | |
f = open("tabla_frecuencias.txt", "w") | |
for i in codigos: | |
f.write("%s %s\n" %(i[0], i[2])) | |
def escribe_frase_original(frase): | |
"""escribe la frase original | |
""" | |
f = open("frase_original.txt", "w") | |
f.write(frase) | |
def escribe_codigos(frase, codigos): | |
"""escribe la frase codificada | |
""" | |
f = open("frase_codificada.txt", "w") | |
for i in frase: | |
f.write("%s" %codigos[i]) | |
f.close() | |
f = open("frase_codificada.txt") | |
texto = f.read() | |
return len(texto) | |
def decodificar(raiz): | |
"""decodifica | |
""" | |
f = open("frase_codificada.txt") | |
frase_codificada = f.read() | |
#print frase_codificada | |
frase_encontrada = "" | |
padre = raiz | |
while len(frase_codificada)>0: | |
s = str(frase_codificada[0]) | |
frase_codificada = frase_codificada[1:] | |
if s == "0": | |
raiz = raiz.der | |
else: | |
raiz = raiz.izq | |
if raiz.der == None and raiz.izq == None: | |
frase_encontrada = frase_encontrada+raiz.simbolo | |
raiz = padre | |
return frase_encontrada | |
def codigos(hojas): | |
"""obtiene los codigos haciendo un recorrido desde la | |
hoja a la raiz | |
""" | |
lista = [] | |
dic = {} | |
for i in hojas: | |
byte = "" | |
padre = i.padre | |
actual = i | |
while True: | |
if padre: | |
pass | |
else: | |
break | |
if actual == padre.izq: | |
byte = byte + "1" | |
else: | |
byte = byte + "0" | |
actual = padre | |
padre = padre.padre | |
lista.append((i.simbolo, i.peso, byte[::-1])) | |
dic[i.simbolo]=byte[::-1] | |
if DEBUG: | |
print lista, dic | |
return lista, dic | |
def obtiene_hojas(nodos): | |
"""obtiene las hojas del arbol, recibe como entrada | |
toda la lista de nodos | |
""" | |
hojas = [] | |
for nodo in nodos: | |
if nodo.der == None and nodo.izq == None: | |
hojas.append(nodo) | |
if DEBUG: | |
print "Numero de hojas %s" %(len(hojas)) | |
return hojas | |
def recorrer(padre, lista_nodos, lista_pesos): | |
"""recorrido en preorden solo para obtener la lista de | |
todos los nodos | |
""" | |
if padre != None: | |
lista_nodos.append(padre) | |
lista_pesos.append(padre.peso) | |
recorrer(padre.izq, lista_nodos, lista_pesos) | |
recorrer(padre.der, lista_nodos, lista_pesos) | |
return lista_nodos, lista_pesos | |
def crea_arbol(frec, ordenados): | |
"""hace el arbol de acuerdo a las frecuencias | |
regresa la raiz del arbol | |
""" | |
nodos = [] | |
for i in ordenados: | |
nodos.append(Nodo(i, frec[i])) | |
while True: | |
if len(nodos)>1: | |
izq = nodos[0] | |
nodos = nodos[1:] | |
der = nodos[0] | |
nodos = nodos[1:] | |
x = Nodo(str(izq.simbolo)+str(der.simbolo), izq.peso + der.peso) | |
x.izq = izq | |
x.der = der | |
nodos.append(x) | |
der.padre = x | |
izq.padre = x | |
nodos = sorted(nodos, key=lambda x: x.peso) | |
if DEBUG: | |
print "*papa %s %s, *izq %s %s *der %s %s" %(x.peso, x.simbolo, | |
x.izq.peso,x.izq.simbolo, | |
x.der.peso,x.der.simbolo) | |
elif len(nodos)==1: | |
padre = Nodo(str(izq.simbolo)+str(der.simbolo), izq.peso + der.peso) | |
padre.izq = izq | |
padre.der = der | |
nodos = [] | |
if DEBUG: | |
print "*papa %s %s, *izq %s %s *der %s %s" %(padre.peso, padre.simbolo, padre.izq.peso,padre.izq.simbolo, padre.der.peso, padre.der.simbolo) | |
else: | |
break | |
return padre | |
def frecuencia(frase): | |
"""obtiene la frecuencia de los simbolos | |
en la frase | |
""" | |
frec = {} | |
for l in frase: | |
if not(l in frec): | |
frec[l] = frase.count(l) | |
ordenados = sorted(frec, key=frec.get) | |
if DEBUG: | |
print "Largo de la cadena %s" %len(frec) | |
print "Frecuencias" | |
for i in ordenados: | |
print "%s: %s" %(i, frec[i]) | |
return frec, ordenados | |
def huffman(frase): | |
inicio = time.time() | |
len_original = len(frase)*4 | |
#frase = "this is an example of a huffman tree" | |
escribe_frase_original(frase)#se escribe un archivo de texto | |
#frase = "j'aime aller sur le bord de l'eau les jeudis ou les jours impairs" | |
frec, ordenados = frecuencia(frase) #tabla de frecuencias | |
raiz = crea_arbol(frec, ordenados) | |
#recorrido en preorden | |
lista_pesos = [] | |
lista_nodos = [] | |
lista_nodos, lista_pesos = recorrer(raiz, lista_nodos, lista_pesos) | |
print "Recorrido en preorden %s" %lista_pesos | |
#obtengo hojas del arbol | |
hojas = obtiene_hojas(lista_nodos) | |
#se obtienen los codigos para cada simbolo | |
lista_codigos, dic_codigos = codigos(hojas) | |
#se escribe una tabla con sus frecuencias | |
escribe_tabla(lista_codigos) | |
#se pasa el texto normal a los codigos encontrados | |
len_codificado = escribe_codigos(frase, dic_codigos) | |
#se decodifica | |
frase_decodificada = decodificar(raiz) | |
print "La frase encontrada: %s" %frase_decodificada | |
tiempo_total = time.time() - inicio | |
return tiempo_total, len_original, len_codificado | |
def main(): | |
"""funcion principal | |
""" | |
largos = genera_largos() | |
dic1 = genera_peor_caso(largos) | |
for i in dic1: | |
print huffman(dic1[i]) | |
dic2 = probabilidad_tipica(largos) | |
lista_malos = [] | |
lista_tipicos = [] | |
lista_m_orig = [] | |
lista_m_cod = [] | |
lista_b_orig = [] | |
lista_b_cod = [] | |
lista_b_ratio = [] | |
lista_m_ratio = [] | |
rangos = [] | |
for key in sorted(dic1.iterkeys()): | |
rangos.append(key) | |
malo, len_original_m, len_codificado_m = huffman(dic1[key]) | |
tipico, len_original_b, len_codificado_b = huffman(dic1[key]) | |
print "malo %s: %s" % (key, malo) | |
print "bueno %s: %s" % (key, tipico) | |
lista_malos.append(malo) | |
lista_tipicos.append(tipico) | |
lista_m_orig.append(len_original_m) | |
lista_m_cod.append(len_codificado_m) | |
lista_b_orig.append(len_original_b) | |
lista_b_cod.append(len_codificado_b) | |
lista_b_ratio.append(len_original_m/len_codificado_m) | |
lista_m_ratio.append(len_original_b/len_codificado_b) | |
#plt.plot(rangos, lista_b_orig,'r^', rangos, lista_b_cod,'g^') | |
plt.plot(rangos, lista_malos,'r--', rangos, lista_tipicos,'g--') | |
plt.legend(('Worst case', 'Normal case'), | |
'upper center', shadow=True) | |
#plt.legend(('Normal', 'Huffman'), | |
# 'upper center', shadow=True) | |
plt.ylabel('Time') | |
plt.xlabel('Text length') | |
plt.show() | |
main() |
References
Huffman compression
Huffman coding
El mejor caso en realidad no es un caso típico ;) Un caso típico saldría de textos de verdad. Sería bueno guardar lo comprimido en un archivo (binario) para calcular la tasa de compresión en términos de los largos de los archivos de entrada versus salida. Pero está todo bastante bien. Van 7+7 pts.
ResponderEliminar