Code the Huffman!


13

Altrimenti sbufferà e sbufferà e farà esplodere la tua casa!

Questo era completamente irrilevante. Questa sfida riguarda in realtà la codifica Huffman . L'essenza di ciò è la frequenza dei caratteri in un dato testo che viene utilizzata per accorciarne la rappresentazione. In altre parole, diciamo che il nostro alfabeto è aattraverso ze lo spazio. Sono 27 personaggi. Ognuno di essi può essere codificato in modo univoco in soli 5 bit perché 5 bit hanno spazio sufficiente per 32 caratteri. Tuttavia, in molte situazioni (come l'inglese o le lingue in generale), alcuni personaggi sono più frequenti di altri. Possiamo usare meno bit per i caratteri più frequenti e (forse) più bit per i caratteri meno frequenti. Fatto bene, c'è un risparmio complessivo nel numero di bit e il testo originale può ancora essere ricostruito in modo univoco.

Prendiamo ad esempio "questa domanda riguarda la codifica huffman". Questo testo è lungo 37 caratteri, che normalmente sarebbero 37 * 8 = 296 bit, sebbene solo 37 * 5 = 185 bit se utilizzassimo solo 5 bit per ciascun carattere. Tienilo a mente.

Ecco una (sorta) tabella di ogni personaggio e le loro frequenze nel testo, ordinate dal più al meno frequente (dove _ sta per uno spazio):

_ 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

Una codifica ottimale associata potrebbe essere:

_ 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

Dovrebbe essere immediatamente chiaro che questa sarà una codifica migliore rispetto all'uso di 5 bit per ogni carattere. Scopriamo quanto è meglio, però!

145 bit , rispetto a 185! Questo è un risparmio di 40 bit, o poco più del 20%! (Ciò ovviamente presuppone che le informazioni sulla struttura siano disponibili per la decodifica.) Questa codifica è ottimale perché non è possibile eliminare più bit modificando la rappresentazione di qualsiasi carattere.

L'obiettivo

  • Scrivi un programma o una funzione con un parametro che ...
  • Prende input da STDIN (o equivalente) o come singolo argomento.
  • Emetti un codice Huffman ottimale come sopra con i caratteri ordinati per frequenza (l'ordine all'interno di una classe di frequenza non ha importanza).
  • Si può presumere che i caratteri nell'input siano limitati all'intervallo ASCII 32..126più una nuova riga.
  • Si può presumere che l'input non sia più lungo di 10.000 caratteri (idealmente, in teoria, l'input dovrebbe essere illimitato).
  • Il tuo codice dovrebbe finire ragionevolmente veloce. L'esempio sopra riportato non dovrebbe richiedere più di un minuto nel peggiore dei casi. (Questo ha lo scopo di escludere la forza bruta.)
  • Il punteggio è in byte.

Esempi

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

Buona programmazione!


Si noti che questa domanda simile è strettamente correlata, anche al punto che questa è una copia. Tuttavia, finora il consenso su Meta è che quello più vecchio dovrebbe essere considerato un duplicato di questo.


1
Non sono d'accordo con la tua nota: è la stessa domanda che per le risposte esistenti richiede una semplice trasformazione del formato di output e inoltre qualsiasi risposta a questa domanda è automaticamente una risposta alla domanda precedente.
Peter Taylor,

@PeterTaylor: vorrei presentare nuovamente una petizione per riaprire questa domanda. Le specifiche in questo sono migliori (come detto da Martin) e voglio vedere risposte più recenti e migliori, comprese le risposte di Pyth e CJam. Penso che non ci sia nulla di male nel lasciare aperte entrambe le domande perché sono sufficientemente diverse. Solo due dei cinque utenti che hanno pubblicato questa domanda sono stati recentemente su questo sito.
El'endia Starman,

@PeterTaylor: Inoltre, seguendo questo standard , vorrei dire che non penso che le risposte possano essere copiate tra le domande e rimanere competitive. Infine, l'altra domanda ha quattro anni . Sarebbe bello avere una versione nuova.
El'endia Starman,

Nel tuo esempio di this question is about huffman coding, ho contato il numero di bit per essere 145 , non 136.
TFeld

1
Stavo davvero cercando di completare questa sfida in Spoon , ma dopo 2 ore di lavori di lavaggio del cervello ho deciso che sarebbe stato meglio arrendersi ...
Bassdrop Cumberwubwubwub,

Risposte:


2

Pyth, 53 byte

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

Dimostrazione

Ecco una versione che mostra lo stato interno, quindi puoi vedere la codifica in costruzione:

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

Dimostrazione

Copia l'output in un ambiente con linee più ampie per un'immagine più chiara.


4

Python 2, 299 byte

Ecco il mio tentativo di risposta.

I codici Huffman sono diversi dagli esempi forniti, ma dovrebbero comunque essere ottimali.

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 byte

tabulatecrea una tabella delle frequenze. huffmandictprende un elenco di simboli e probabilità per ciascun simbolo e calcola il codice.

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

Rubino, 189 180 byte

Lavori in corso.

->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}

È una funzione anonima; assegnalo a qualcosa, per esempio f, e chiamalo con

f["some test string"]`

che restituisce un hash come questo:

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

1

Haskell, 227 byte

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

Esempio di utilizzo:

*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

Come funziona:

ple chiamate fche costruiscono la tabella di Huffman sotto forma di un elenco di (caratteri, codifica) -pairs, ad esempio [ ('a',"0"), ('b',"1") ], ordina la tabella in base alla lunghezza delle codifiche, formatta ciascuna coppia per l'output e unisce le nuove linee intermedie.

fcontrolla innanzitutto il caso di una sola lettera e restituisce la tabella corrispondente. Altrimenti ordina la stringa di input e raggruppa sequenze di caratteri uguali (ad es. "ababa"-> ["aaa","bb"]) e le mappa in coppie (sequence , [(char, "")]), (-> [ ("aaa", [('a',"")]), ("bb", [('b', "")])]. Il primo elemento viene utilizzato per tenere traccia della frequenza, il secondo elemento è un elenco di coppie di un carattere ed è la codifica (che inizialmente è vuota). Queste sono tutte le tabelle di Huffman a singolo elemento come previsto da pe sono combinate da ge h.

gordina l'elenco di coppie sulla lunghezza del primo elemento, ovvero la frequenza e le chiamate h. hcombina le tabelle di Huffman dei primi due elementi, concatenando le frequenze e mettendo un 0( 1) davanti a ogni elemento della prima (seconda) tabella. Concatena entrambe le tabelle. Chiama di gnuovo, fermati quando è rimasto un solo elemento, elimina la parte di frequenza e restituisce l'intero tavolo Huffman.


1

K (ngn / k) , 78 byte

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

Provalo online!

restituisce un elenco di stringhe per la stampa

h::0#'xcrea un elenco vuoto per ogni carattere (tecnicamente, rimodella ogni carattere per lunghezza 0). memorizzeremo lì i codici huffman invertiti. usiamo ::invece che :per assegnazione per rendere hglobale in modo che sia visibile nelle sotto-funzioni.

.=x è un elenco di elenchi: gli indici della stringa raggruppati per valore di carattere

(#1_) è una funzione che restituisce verità se la lunghezza dell'argomento è> 1 (tecnicamente "lunghezza di 1 goccia di ...")

(#1_){... }/significa: mentre l'argomento ha lunghezza> 1, continua ad applicare la funzione parentesi graffa

x@<#'x ordina l'argomento per lunghezza

0 2_ tagliarlo in una testa a 2 elementi e una coda

{h[x],:!2;y,,,/x}aggiornare haggiungendo 0 e 1 agli indici contenuti nella testa; restituisce la coda con la testa come un singolo elemento

(?,/'x,'" ",'|'$h)(?x)?>#'=xinvertire ciascuno h, ordinare, univoco, anteporre caratteri corrispondenti e formattare bene


0

JavaScript (ES6) 279

In sostanza, l'algoritmo di base da Wikipedia. Probabilmente posso fare di meglio.

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)}

Più leggibile nello snippet di seguito

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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.