Codifique o Huffman!


13

Ou então ele bufará e soprará sua casa!

Isso foi completamente irrelevante. Esse desafio é realmente sobre a codificação de Huffman . A essência disso é que a frequência de caracteres em um determinado texto é utilizada para diminuir sua representação. Em outras palavras, digamos que nosso alfabeto é aatravés do zespaço. São 27 caracteres. Cada um deles pode ser codificado exclusivamente em apenas 5 bits, porque 5 bits têm espaço suficiente para 32 caracteres. No entanto, em muitas situações (como inglês ou idiomas em geral), alguns caracteres são mais frequentes que outros. Podemos usar menos bits para os caracteres mais frequentes e (talvez) mais bits para os caracteres menos frequentes. Feito corretamente, há uma economia geral no número de bits e o texto original ainda pode ser reconstruído exclusivamente.

Vamos pegar "esta pergunta é sobre a codificação de Huffman" como exemplo. Esse texto tem 37 caracteres, o que seria normalmente 37 * 8 = 296 bits, embora apenas 37 * 5 = 185 bits se usarmos apenas 5 bits para cada caractere. Tenha isso em mente.

Aqui está uma tabela (sorta) de cada caractere e suas frequências no texto, classificadas da mais para a menos frequente (onde _ representa um espaço):

_ 5
i 4
n 3
o 3
s 3
t 3
u 3
a 2
f 2
h 2
b 1
c 1
d 1
e 1
g 1
m 1
q 1

Uma codificação ótima associada pode ser:

_ 101
i 011
n 1100
o 1101
s 1110
t 1111
u 001
a 10011
f 0001
h 0101
b 00000
c 00001
d 01000
e 01001
g 10000
m 10001
q 10010

Deve ficar claro imediatamente que essa codificação será melhor do que usar 5 bits para cada caractere. Vamos descobrir o quão melhor, porém!

145 bits , em comparação com 185! Isso economiza 40 bits, ou pouco mais de 20%! (É claro que isso pressupõe que as informações sobre a estrutura estejam disponíveis para decodificação.) Essa codificação é ideal porque não é possível eliminar mais bits alterando a representação de qualquer caractere.

A tarefa

  • Escreva um programa ou função com um parâmetro que ...
  • Recebe entrada de STDIN (ou equivalente) ou como um único argumento.
  • Produza uma codificação Huffman ideal, como acima, com os caracteres classificados por frequência (a ordem dentro de uma classe de frequência não importa).
  • Você pode supor que os caracteres na entrada sejam restritos ao intervalo ASCII 32..126mais uma nova linha.
  • Você pode supor que a entrada não tenha mais que 10.000 caracteres (idealmente, em teoria, a entrada deve ser ilimitada).
  • Seu código deve terminar razoavelmente rápido. O exemplo dado acima não deve demorar mais do que um minuto, na pior das hipóteses. (Isso pretende excluir a força bruta.)
  • A pontuação está em bytes.

Exemplos

x
---
x 0

xxxxxxxxx
---
x 0

xxxxxxxxy
---
x 0
y 1 (these may be swapped)

xxxxxyyyz
---
x 0
y 10
z 11

uuvvwwxxyyzz
---   (or) 
u 000      000
v 001      001
w 100      010
x 101      011
y 01       10
z 11       11

this question is about huffman coding
---
  101
i 011
n 1100
o 1101
s 1110
t 1111
u 001
a 10011
f 0001
h 0101
b 00000
c 00001
d 01000
e 01001
g 10000
m 10001
q 10010

Feliz codificação!


Observe que essa pergunta semelhante está intimamente relacionada, mesmo a ponto de ser uma duplicata. No entanto, o consenso até agora no Meta é que o mais antigo deve ser considerado uma duplicata deste.


1
Discordo da sua observação: é a mesma pergunta que para as respostas existentes requer uma simples transformação do formato de saída e, além disso, qualquer resposta a essa pergunta é automaticamente uma resposta à pergunta anterior.
Peter Taylor

@ PeterTaylor: Gostaria de pedir novamente que você reabra esta pergunta. A especificação neste é melhor (como dito por Martin) e quero ver respostas melhores e mais novas, incluindo respostas Pyth e CJam. Acho que não há mal nenhum em deixar as duas questões em aberto porque são suficientemente diferentes. Apenas dois dos cinco usuários que postaram nessa pergunta estiveram neste site recentemente.
El'endia Starman 28/10/2015

@ PeterTaylor: Além disso, seguindo esse padrão , gostaria de dizer que não acho que as respostas possam ser copiadas entre as perguntas e permanecer competitivas. Finalmente, a outra pergunta tem quatro anos . Seria bom ter uma versão nova.
El'endia Starman 28/10/2015

No seu exemplo this question is about huffman coding, contei que o número de bits era 145 , não 136. #
TFeld 29/10

1
Eu estava realmente tentando completar este desafio na colher , mas após 2 horas de brainfuckery I decidiu que seria melhor desistir ...
Bassdrop Cumberwubwubwub

Respostas:


2

Pitão, 53 bytes

jo_/zhNee.WtHa>JohNZ2+shKC<J2]s.b+RYNeKU2m,/zd]+d\ {z

Demonstração

Aqui está uma versão que mostra o estado interno, para que você possa ver a codificação sendo construída:

jo_/zhNee.WtHvp+`a>JohNZ2+shKC<J2]s.b+RYNeKU2bm,/zd]+d\ {z

Demonstração

Copie a saída para um ambiente com linhas mais amplas para obter uma imagem mais nítida.


4

Python 2, 299 bytes

Aqui está minha tentativa de resposta.

Os códigos de Huffman são diferentes dos exemplos dados, mas ainda devem ser ótimos.

i=raw_input();m=n=[(c,i.count(c))for c in set(i)]
while n[1:]:n.sort(key=lambda x:(x[1]));(a,b),(c,d)=n[:2];n=[((a,c),b+d)]+n[2:]
n=n[0][0]
r=[]
def a(b,s):
 if b[1:]:a(b[0],s+'0');a(b[1],s+'1')
 else:r.append(b+(s if s[1:]else s+'0'))
a(n,' ')
for y in sorted(r,key=lambda x:-dict(m)[x[0]]):print y

2

Matlab, 116 bytes

tabulatefaz uma tabela de frequências. huffmandictpega uma lista de símbolos e probabilidades para cada símbolo e calcula o código.

t=tabulate(input('')');
d=huffmandict(t(:,1),cell2mat(t(:,3))/100);
for i=1:size(d,1);disp([d{i,1},' ',d{i,2}+48]);end

2

Rubi, 189 180 bytes

Trabalho em progresso.

->s{m=s.chars.uniq.map{|c|[c,s.count(c)]}
while m[1]
(a,x),(b,y),*m=m.sort_by &:last
m<<[[a,b],x+y]
end
h={}
f=->q="",c{Array===c&&f[q+?0,c[0]]&&f[q+?1,c[1]]||h[c]=q}
f[m[0][0]]
h}

É uma função anônima; atribua a algo, por exemplo f, e chame-o com

f["some test string"]`

que retorna um hash assim:

{"t"=>"00", "g"=>"0100", "o"=>"0101", " "=>"011", "e"=>"100", "n"=>"1010", "i"=>"1011", "m"=>"1100", "r"=>"1101", "s"=>"111"}

1

Haskell, 227 bytes

import Data.List
s=sortOn.(length.)
f x|[c]<-nub x=[(c,"0")]|1<2=g[(a,[(a!!0,"")])|a<-group$sort x]
g=h.s fst
h[x]=snd x
h((a,b):(c,d):e)=g$(a++c,map('0'#)b++map('1'#)d):e
n#(a,b)=(a,n:b)
p=unlines.map(\(a,b)->a:" "++b).s snd.f

Exemplo de uso:

*Main> putStr $ p "this question is about huffman coding"
u 000
i 011
  101
a 0010
f 0011
h 1000
s 1100
t 1101
n 1110
o 1111
d 01000
e 01001
b 01010
c 01011
q 10010
g 100110
m 100111

Como funciona:

pchama fque cria a tabela Huffman na forma de uma lista de pares (caracteres, codificação), por exemplo [ ('a',"0"), ('b',"1") ], classifica a tabela pelo comprimento das codificações, formata cada par para saída e junta-se às novas linhas intermediárias.

fprimeiro verifica a letra maiúscula e retorna a tabela correspondente. Caso contrário, classifica a sequência de entrada e agrupa seqüências de caracteres iguais (por exemplo "ababa"- - ["aaa","bb"]) e as mapeia em pares (sequence , [(char, "")])(-> [ ("aaa", [('a',"")]), ("bb", [('b', "")])]. O primeiro elemento é usado para acompanhar a frequência, o segundo elemento é uma lista de pares de um caractere e é codificação (que está inicialmente vazia) .Todas as tabelas Huffman de elemento único conforme o esperado pe são combinadas por ge h.

gclassifica a lista de pares no comprimento do primeiro elemento, ou seja, a frequência e as chamadas h. hcombina as tabelas Huffman dos dois primeiros elementos, concatenando as frequências e colocando um 0( 1) na frente de cada elemento da primeira (segunda) tabela. Concatene ambas as tabelas. Ligue gnovamente, pare quando houver um único elemento, jogue fora a parte da frequência e retorne a tabela completa de Huffman.


1

K (ngn / k) , 78 bytes

{h::0#'x;(#1_){{h[x],:!2;y,,,/x}.0 2_x@<#'x}/.=x;(?,/'x,'" ",'|'$h)(?x)?>#'=x}

Experimente online!

retorna uma lista de strings para impressão

h::0#'xcria uma lista vazia para cada caractere (tecnicamente, remodela cada caractere no comprimento 0). vamos armazenar os códigos Huffman invertidos lá. usamos em ::vez de :atribuição para tornar hglobal, para que fique visível nas sub-funções.

.=x é uma lista de listas - os índices da sequência agrupada por valor de caractere

(#1_) é uma função que retorna verdade se o comprimento do argumento for> 1 (tecnicamente "comprimento de 1 gota de ...")

(#1_){... }/significa: enquanto o argumento tiver comprimento> 1, continue aplicando a função de chaves

x@<#'x classificar o argumento por comprimento

0 2_ cortá-lo em uma cabeça de 2 elementos e uma cauda

{h[x],:!2;y,,,/x}atualizar hanexando 0 e 1 aos índices contidos no cabeçalho; devolver a cauda com a cabeça como um único elemento

(?,/'x,'" ",'|'$h)(?x)?>#'=xinverta cada um h, ordene, coloque caracteres precedentes e adicione formatos


0

JavaScript (ES6) 279

Essencialmente, o algoritmo básico da Wikipedia. Eu provavelmente posso fazer melhor.

f=s=>{for(n=[...new Set(s)].map(c=>({c:c,f:[...s].filter(x=>x==c).length}));n[1];n.push({l:a=n.pop(),r:b=n.pop(),f:a.f+b.f,c:a.c+b.c}))n.sort((a,b)=>b.f-a.f);t=(n,b)=>(n.b=b,n.r)?(t(n.l,b+0),t(n.r,b+1)):o.push(n);t(n[0],'',o=[]);return o.sort((a,b)=>b.f-a.f).map(x=>x.c+' '+x.b)}

Mais legível dentro do snippet abaixo

f=s=>{
  for(n=[...new Set(s)].map(c=>({c:c,f:[...s].filter(x=>x==c).length}));
      n[1];
      n.push({l:a=n.pop(),r:b=n.pop(),f:a.f+b.f,c:a.c+b.c}))
    n.sort((a,b)=>b.f-a.f);
  t=(n,b)=>(n.b=b,n.r)?(t(n.l,b+0),t(n.r,b+1)):o.push(n);
  t(n[0],'',o=[]);
  return o.sort((a,b)=>b.f-a.f).map(x=>x.c+' '+x.b)
}

//TEST
console.log=x=>O.innerHTML+=x+'\n'

test=['xxxxxxxxy','uuvvwwxxyyzz','this question is about huffman coding']
.forEach(t=>console.log(t+'\n'+f(t).join`\n`+'\n'))
<pre id=O></pre>

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.