Codificando um conjunto de índices de um intervalo

Precisamos codificar as informações sobre a presença ou ausência de elementos em uma lista de triângulos.

Vamos abordar isso concentrando-nos no problema equivalente de codificar um conjunto (um contêiner que armazena elementos não repetidos, otimizados para detectar a presença ou ausência de elementos específicos) que só pode conter elementos pertencentes à faixa inteira: [0, número de triângulos - 1], ou seja, o intervalo de índice da lista de triângulos.


Objetivo: codificar este conjunto usando o número mínimo de bits.

Descrição

Vamos olhar para a abordagem trivial, que em muitos casos pode ser ideal:

Ele armazenaria:

  • O número de elementos no intervalo (com 31 bits, uma vez que as listas em Java são limitadas a 2-31 - 1 elementos).
  • Seguido por 1 bit para cada índice, indicando se o elemento está presente no conjunto ou não.

Esta é a implementação que a primeira versão do compressor de imagem fractal tinha, para armazenar os dados das divisões e fusões das triangulações.

Embora não fosse ideal, uma vez que à medida que as iterações das divisões progrediam, o número de elementos a serem armazenados diminuiu, tornando-se uma porcentagem muito pequena do total, caso em que a codificação trivial pode ser substancialmente melhorada.


Agora o objetivo é melhorar essa codificação trivial, para ver se uma codificação mais compactada em termos do número de bits usados pode ser obtida.


Primeira abordagem Quando o número de elementos no conjunto é muito menor do que o tamanho do intervalo, podemos considerar armazenar os índices presentes.

Neste caso, o uso seria:

  • 31 bits para o número de elementos no intervalo.
  • 31 bits para o número de elementos presentes no conjunto.
  • 31 bits * o número de elementos presentes no conjunto.


Segunda abordagem . Melhoramos o número de bits por elemento armazenado:

  • 31 bits para o número de elementos no intervalo. O número de bits do maior elemento no intervalo ( numBitsElem ( ) serão calculados.
  • 5 bits para armazenar numBitsElem .Então, se o número de elementos no intervalo fosse 1023, um 10 seria armazenado nesses 5 bits.
  • numBitsElem para armazenar o número de elementos no conjunto.
  • numBitsElem * o número de elementos presentes no conjunto.

Bem, pouco a pouco estamos nos aproximando de uma codificação melhor...


Terceira aproximação . Percebemos que, se classificarmos o conjunto em ordem crescente, os deltas (diferenças entre elementos consecutivos) provavelmente podem ser codificados com menos bits do que um elemento absoluto do intervalo:

  • 31 bits para o número de elementos no intervalo.
  • Calculamos o menor numBitsElem para codificar um elemento.Também calculamos a lista de deltas (lista de diferenças entre elementos consecutivos da lista ordenada de elementos no conjunto).E, finalmente, também calculamos numBitsDelta
  • 5 bits para armazenar numBitsElem .
  • 5 bits para armazenar numBitsDelta .
  • numBitsElem para armazenar o número de elementos no conjunto.
  • numBitsDelta * o número de elementos no conjunto.


Bem, parece que já estamos chegando à codificação ideal, certo?...

Vamos continuar...

Quarta aproximação . Nós nos concentramos no fato de que pode haver um número muito maior de deltas menores, e talvez apenas alguns muito maiores, que quebram o numBitsDelta .

Nestas condições, podemos encontrar um numoptimumBitsDelta menor que o máximo numMaxBitsDelta , que por sua vez é menor ou igual a numBitsElem .

Mas como codificamos um elemento delta que excede numoptimumBitsDelta ?

Bem... uma vez que os deltas nunca serão 0 (não há elementos repetidos em um conjunto), podemos usar o valor 0 para indicar que é um valor especial.

E que 0 seria seguido pelo valor do delta, mas codificado desta vez com numMaxBitsDelta .O número total de bits pode ser calculado da seguinte forma:

  • 31 bits para o número de elementos no intervalo. Calculamos o menor numBitsElem para poder codificar um elemento. Também calculamos a lista de deltas (lista de diferenças entre elementos consecutivos da lista ordenada de elementos no Conjunto).
  • 31 bits, para armazenar o número de elementos no conjunto.
  • 5 bits para armazenar numoptimumBitsDelta .
  • 5 bits para armazenar numMaxBitsDelta .
  • numoptimumBitsDelta * o número de elementos no conjunto.
  • numMaxBitsDelta * o número de deltas que excedem numoptimumBitsDelta .


Já atingimos a codificação ideal?

Quinta aproximação Bem... já que já programámos tudo isto...Podemos pensar na situação complementar...Seria possível que codificar o complemento do conjunto com o mesmo algoritmo produziria resultados mais comprimidos?

O complemento do conjunto é entendido como outro conjunto, desarticulado do original, cuja união com o conjunto original é toda a gama.

Ou seja, o conjunto complementar teria todos os elementos do intervalo ausentes no conjunto original, e nenhum deles no conjunto original.

A beleza é que a partir de um conjunto, é muito fácil obter o conjunto complementar e, portanto, poderíamos obter o conjunto original, tendo codificado o conjunto complementar.

A aplicação óbvia é um conjunto que tem todos os elementos do intervalo, exceto um, ou alguns...

Bem... se armazenarmos apenas os ausentes (o conjunto complementar), ele pode ser armazenado em um tamanho muito menor, certo?

A codificação neste caso poderia ser assim:

  • 31 bits para o número de elementos no intervalo. Calculamos o menor numBitsElem Também calculamos a lista de deltas (lista de diferenças entre elementos consecutivos da lista ordenada de elementos no Conjunto).
  • 1 bit, para armazenar se nós armazenamos o conjunto original, ou o complemento.Esta é a parte extra.
  • 31 bits, para armazenar o número de elementos no conjunto.
  • 5 bits para armazenar numoptimumBitsDelta .
  • 5 bits para armazenar numMaxBitsDelta .
  • numoptimumBitsDelta * o número de elementos no conjunto.
  • numMaxBitsDelta * o número de deltas que excedem numoptimumBitsDelta nas posições em que aparecem.

Bem... agora estamos a falar.

Mas estou ciente de que, se todas essas maravilhas puderem ser feitas, o que não poderia ser feito procurando outros padrões, ou simplesmente usando um compressor sem perdas, como zip ou 7z...


Isso vai ter que esperar, em segundo plano para a próxima vez...

Descrição do código

O decodificador é trivial... você só precisa ler os parâmetros do bitstream e aplicar as ações necessárias para obter o conjunto original.


Vou me concentrar no algoritmo do codificador, tentando otimizar o número de operações.

A ideia é calcular o número de bits que ocuparia com cada tipo de codificação e manter o ideal.

Como não faria sentido usar a codificação delta se ela acabasse ocupando mais do que a codificação trivial.

Então... calculamos o que a codificação trivial ocuparia.

E também... calculamos os parâmetros ótimos da codificação delta-tipo mais elaborada que explicamos na seção anterior.


Isso seria feito calculando o número de bits que a codificação ocuparia para as combinações de parâmetros:

  • numoptimumBitsDelta
  • numMaxBitsDelta Isto está resolvido.
  • Use o conjunto original ou complementar.


Para evitar a complexidade computacional excessiva em encontrar parâmetros ótimos (que poderiam ser penalizados usando grandes intervalos), fazemos o seguinte:

O conjunto complementar é calculado apenas uma vez e armazenado.

As listas delta do conjunto original e do conjunto complementar são calculadas apenas uma vez e armazenadas.

Para calcular o número de bits produzidos pelas diferentes combinações de parâmetros:

Calculamos o histograma do número de bits nas listas delta apenas uma vez. Com base neste histograma, calcular o número total de bits é fácil e a complexidade computacional desses cálculos é muito menor do que se nós "simulamos" codificação elemento por elemento.


Uma vez obtida a combinação ideal de parâmetros para codificação delta, seu tamanho é comparado ao da codificação trivial, e o melhor dos dois é escolhido.

Downloads