Package: pylz78
Trabalho Prático 1Algoritmos 2 - DCC - ICEx - UFMGPedro Tavares de CarvalhoEsse trabalho tem como objetivo a implementação de um compressor/decompressor padrãolz78, utilizando uma trie compacta como estrutura auxiliar. Esse algoritmo de compressão implica na representação do texto como um conjunto de prefixos em ordem, identificando cada sequência como um prefixo existente acoplado a um caractere novo do texto.Em pseudocódigo, o algoritmo se dá assim:def encodeLZ78(texto):
	prefixos = map()
	prefixos.add('', 0)
	cadeia = ''
	indice = 1
	for caractere in texto:
		if cadeia + caractere in prefixos: 	// se a cadeia com o caractere
        									// já existe nos prefixos
			cadeia = cadeia + caractere 	// adicione mais um caractere
            								// à cadeia
			continue
		saida(caractere, prefixos[cadeia]) 	// caso não exista, 
											// adicione à saída a nova
                                            // sequencia que tem que ser
                                            // impressa

		prefixos.add(cadeia + caractere, indice) 	// adicione o prefixo
													// atual à cadeia
		indice = indice + 1 // aumente o índice
		cadeia = ''No caso dessa implementação, o mapaprefixosde dará por uma trie compacta, porém a lógica da compressão continua a mesma.Essa substituição tem como objetivo diminuir a memória gasta com guardar os prefixos já existentes, pois um mapa tem que guardar os prefixos completos, porém a trie compacta é construída de forma a utilizar os prefixos como parte da indexação da mesma, diminuindo a repetição de prefixos comuns.A decompressão, porém, traz alguma dificuldade. Apesar de algoritmicamente simples, ela exige acesso aos índices já concebidos, e com a estrutura da trie esse acesso possui complexidade alta, $\mathcal O(n)$, dado que a busca por índices é basicamente uma busca em profundidade/largura o que incapacita a mesma de fazer o processo de decompressão eficientemente.ImplementaçãoEm um primeiro momento, foi implementado o algoritmo com um dicionário como estrutura auxiliar, apenas para prova de contexto. Essa implementação foi relativamente simples.TrieEm termos da implementação da trie, os algoritmos foram baseados nos vistos em sala de aula, implementados à risca.InserçãoO algoritmo de inserção é descrito abaixodef inserir(node, palavra, valor):
	indice_final, filho = node.comparar_prefixos(palavra)
	if !filho:
    	node.adicionar_filho(palavra, nova_folha(valor))
    	if node.is_folha:
    		node.adicionar_filho('', nova_folha(node.valor))
    		node.is_folha = false
    		node.valor = Null
    else if indice_final == palavra.tamanho():
    	inserir(filho, palavra[:indice_final], valor)
    else:
    	temp = filho
    	node.remover(filho)
    	chave_resto_filho = filho.chave[:indice_final]
    	no = novo_no()
    	filho.chave = filho.chave[indice_final:]
    	node.adicionar_filho(chave_resto_filho, no)
    	no.adicionar_filho(filho.chave, filho)
    	no.adicionar_filho(palavra[indicie_final:], nova_folha(valor))A especificação do funcionamento está no código, mas se entende que existem três casos:Caso, ao adicionar uma palavra ao nó, essa palavra não compartilhe prefixos com os filhos desse nó, crie um nó filho com a palavra como valor.Nesse caso ainda, se o nó no qual a palavra está sendo adicionada era uma folha, estão crie outro nó filho, com a sequência vazia como valor.Caso dentre os filhos exista um prefixo da palavra em questão, adicione o resto dessa palavra ao filho.Caso um dos filhos compartilhe um prefixo com a palavra em questão, crie um novo nó com o prefixo compartilhado, e adicione dois nós a esse, com o resto da palavra e o resto do prefixo.Esse algoritmo constrói a trie a partir de um nó vazio, com o seu valor colocado como a cadeia vazia.RemoçãoComo não havia necessidade de remoção de itens da estrutura, a remoção não foi implementada.BuscaA busca se dá de forma simples. A cada nó, acha a chave que compartilha um prefixo com a palavra a ser buscada. Se essa chave não existir, retorne -1. Se ela existir, repita a busca com o nó correspondente à chave, e com a palavra sem o prefixo da chave.CompressãoA compressão foi feita respeitando o pseudocódigo descrito na introdução do relatório, apenas substituindo o mapa utilizado para os prefixos pela implementação de trie compacta e implementando a saída como uma saída binária.A saída do código gera um arquivo em que os índices de cada prefixo e os caracteres correspondentes a ele possuem um número específico debytes, passado como parâmetro pela interface de linha de comando.DecompressãoA estrutura de dados utilizada na decompressão foi simplesmente um vetor de cadeias. A estrutura é ótima pois inserção e acesso é $\mathcal O(1)$, e a utilização de memória não é tão grande.Interface com o usuárioA interface com o usuário foi feita utilizando a bibliotecaargparse, da bibliotecadefaultda linguagem.