¡Codifique al Huffman!

13

¡De lo contrario, resoplará y soplará y derribará tu casa!

Eso fue completamente irrelevante. Este desafío es en realidad sobre la codificación de Huffman . La esencia de esto es la frecuencia de caracteres en un texto dado que se utiliza para acortar su representación. En otras palabras, digamos que nuestro alfabeto es a através zy el espacio. Eso es 27 personajes. Cada uno de ellos puede codificarse de forma única en solo 5 bits porque 5 bits tienen espacio suficiente para 32 caracteres. Sin embargo, en muchas situaciones (como el inglés o los idiomas en general), algunos caracteres son más frecuentes que otros. Podemos usar menos bits para los caracteres más frecuentes y (quizás) más bits para los caracteres menos frecuentes. Bien hecho, hay un ahorro general en el número de bits y el texto original aún puede reconstruirse de forma única.

Tomemos "esta pregunta es sobre la codificación de huffman" como ejemplo. Este texto tiene 37 caracteres, lo que normalmente sería 37 * 8 = 296 bits, aunque solo 37 * 5 = 185 bits si solo usamos 5 bits para cada carácter. Ten eso en mente.

Aquí hay una tabla (más o menos) de cada carácter y sus frecuencias en el texto, ordenada de más a menos frecuente (donde _ representa un espacio):

_ 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 codificación óptima asociada podría 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

Debe quedar claro de inmediato que esta será una mejor codificación que simplemente usar 5 bits para cada carácter. ¡Pero descubramos cuánto mejor!

145 bits , en comparación con 185! ¡Eso es un ahorro de 40 bits, o un poco más del 20%! (Por supuesto, esto supone que la información sobre la estructura está disponible para la decodificación). Esta codificación es óptima porque no se pueden eliminar más bits al cambiar la representación de cualquier carácter.

La tarea

  • Escriba un programa o función con un parámetro que ...
  • Toma información de STDIN (o equivalente) o como un solo argumento.
  • Produzca una codificación Huffman óptima como la anterior con los caracteres ordenados por frecuencia (el orden dentro de una clase de frecuencia no importa).
  • Puede suponer que los caracteres en la entrada están restringidos al rango ASCII 32..126 más una nueva línea.
  • Puede suponer que la entrada no tiene más de 10,000 caracteres (idealmente, en teoría, la entrada debe ser ilimitada).
  • Su código debe terminar razonablemente rápido. El ejemplo anterior no debería tomar más de un minuto más o menos en el peor. (Esto tiene la intención de descartar la fuerza bruta).
  • La puntuación está en bytes.

Ejemplos

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 codificación!


Tenga en cuenta que esta pregunta similar está estrechamente relacionada, incluso hasta el punto de que esta es un duplicado. Sin embargo, el consenso hasta ahora sobre Meta es que el más antiguo debe considerarse un duplicado de este.

El'endia Starman
fuente
1
No estoy de acuerdo con su nota: es la misma pregunta que para las respuestas existentes requiere una transformación simple del formato de salida, y además, cualquier respuesta a esta pregunta es automáticamente una respuesta a la pregunta anterior.
Peter Taylor
@ PeterTaylor: Me gustaría solicitar nuevamente que vuelva a abrir esta pregunta. La especificación en este es mejor (como dijo Martin) y quiero ver respuestas nuevas y mejores, incluidas las respuestas de Pyth y CJam. Creo que no hay nada de malo en dejar ambas preguntas abiertas porque son lo suficientemente diferentes. Solo dos de los cinco usuarios que publicaron esa pregunta han estado en este sitio recientemente.
El'endia Starman
@PeterTaylor: Además, siguiendo este estándar , me gustaría decir que no creo que las respuestas puedan copiarse entre preguntas y seguir siendo competitivas. Finalmente, la otra pregunta tiene cuatro años . Sería bueno tener una versión nueva.
El'endia Starman
En su ejemplo de this question is about huffman coding, conté el número de bits como 145 , no 136.
TFeld
1
Realmente estaba tratando de completar este desafío en Spoon , pero después de 2 horas de
juerga mental

Respuestas:

2

Pyth, 53 bytes

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

Demostración

Aquí hay una versión que muestra el estado interno, para que pueda ver la codificación que se está creando:

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

Demostración

Copie la salida a un entorno con líneas más anchas para obtener una imagen más clara.

isaacg
fuente
4

Python 2, 299 bytes

Aquí está mi intento de respuesta.

Los códigos de Huffman son diferentes de los ejemplos dados, pero aún así deberían ser óptimos.

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
TFeld
fuente
2

Matlab, 116 bytes

tabulatehace una tabla de frecuencias huffmandicttoma una lista de símbolos y probabilidades para cada símbolo y calcula el 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
falla
fuente
2

Rubí, 189 180 bytes

Trabajo en progreso.

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

Es una función anónima; asignarlo a algo, por ejemplo f, y llamarlo con

f["some test string"]`

que devuelve un hash como este:

{"t"=>"00", "g"=>"0100", "o"=>"0101", " "=>"011", "e"=>"100", "n"=>"1010", "i"=>"1011", "m"=>"1100", "r"=>"1101", "s"=>"111"}
daniero
fuente
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

Ejemplo 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

Cómo funciona:

pllamadas fque construye la tabla Huffman en forma de una lista de pares (caracteres, codificación), por ejemplo [ ('a',"0"), ('b',"1") ], clasifica la tabla por longitud de codificaciones, formatea cada par para la salida y se une con nuevas líneas intermedias.

fprimero comprueba el caso de una letra y devuelve la tabla correspondiente. De lo contrario, clasifica la cadena de entrada y agrupa secuencias de caracteres iguales (por ejemplo, "ababa"-> ["aaa","bb"]) y las asigna a pares (sequence , [(char, "")]), (-> [ ("aaa", [('a',"")]), ("bb", [('b', "")])]. El primer elemento se utiliza para realizar un seguimiento de la frecuencia, el segundo elemento es una lista de pares de un carácter y está codificando (que inicialmente está vacío). Estas son todas las tablas de Huffman de elemento único como se esperaba py están combinadas porg y h.

gordena la lista de pares según la longitud del primer elemento, es decir, la frecuencia y las llamadas h. hcombina las tablas de Huffman de los dos primeros elementos, concatenando las frecuencias y poniendo un 0( 1) delante de cada elemento de la primera (segunda) tabla. Concatenar ambas tablas. Llama de gnuevo, detente cuando quede un solo elemento, tira la parte de frecuencia y devuelve la tabla completa de Huffman.

nimi
fuente
1

K (ngn / k) , 78 bytes

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

Pruébalo en línea!

devuelve una lista de cadenas para imprimir

h::0#'xcrea una lista vacía para cada carácter (técnicamente, da nueva forma a cada carácter a la longitud 0). almacenaremos los códigos invertidos de huffman allí. usamos en ::lugar de :para la asignación para hacer hglobal para que sea visible en subfunciones

.=x es una lista de listas: los índices de la cadena agrupados por valor de caracteres

(#1_) es una función que devuelve verdad si la longitud del argumento es> 1 (técnicamente "longitud de 1 gota de ...")

(#1_){... }/significa: mientras el argumento tiene una longitud> 1, sigue aplicando la función de llaves

x@<#'x ordenar el argumento por longitud

0 2_ cortarlo en una cabeza de 2 elementos y una cola

{h[x],:!2;y,,,/x}actualizar hagregando 0 y 1 a los índices contenidos en el encabezado; devolver la cola con la cabeza como un elemento único

(?,/'x,'" ",'|'$h)(?x)?>#'=xinvierte cada uno de los hcaracteres correspondientes, los ordena, los únicos y los antepone, y los formatea bien

ngn
fuente
0

JavaScript (ES6) 279

Esencialmente, el algoritmo básico de Wikipedia. Probablemente pueda hacerlo mejor.

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

Más legible dentro del fragmento a continuación

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>

edc65
fuente