Codez le Huffman!

13

Ou bien il va souffler et souffler et faire sauter votre maison!

C'était complètement hors de propos. Ce défi concerne en fait le codage Huffman . L'essentiel est que la fréquence des caractères dans un texte donné est utilisée pour raccourcir sa représentation. En d'autres termes, disons que notre alphabet est atraversant zet spatial. Cela fait 27 caractères. Chacun d'eux peut être codé de manière unique en seulement 5 bits car 5 bits ont suffisamment de place pour 32 caractères. Cependant, dans de nombreuses situations (comme l'anglais ou les langues en général), certains caractères sont plus fréquents que d'autres. Nous pouvons utiliser moins de bits pour les caractères les plus fréquents et (peut-être) plus de bits pour les caractères les moins fréquents. Bien fait, il y a une économie globale sur le nombre de bits et le texte original peut toujours être reconstruit de manière unique.

Prenons "cette question concerne le codage de Huffman" comme exemple. Ce texte est de 37 caractères, ce qui serait normalement 37 * 8 = 296 bits, bien que seulement 37 * 5 = 185 bits si nous n'utilisons que 5 bits pour chaque caractère. Garde cela à l'esprit.

Voici un tableau (sorta) de chaque caractère et de leurs fréquences dans le texte, triés du plus au moins fréquent (où _ représente un espace):

_ 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

Un codage optimal associé pourrait être:

_ 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

Il devrait être immédiatement clair que ce sera un meilleur encodage que d'utiliser simplement 5 bits pour chaque caractère. Voyons à quel point mieux, cependant!

145 bits , contre 185! C'est une économie de 40 bits, soit un peu plus de 20%! (Cela suppose, bien entendu, que des informations sur la structure soient disponibles pour le décodage.) Ce codage est optimal car aucun bit ne peut être supprimé en modifiant la représentation d'un caractère.

La tâche

  • Écrivez un programme ou une fonction avec un paramètre qui ...
  • Prend l'entrée de STDIN (ou équivalent) ou comme un seul argument.
  • Générez un codage Huffman optimal comme ci-dessus avec les caractères triés par fréquence (l'ordre dans une classe de fréquence n'a pas d'importance).
  • Vous pouvez supposer que les caractères de l'entrée sont limités à la plage ASCII 32..126plus une nouvelle ligne.
  • Vous pouvez supposer que la saisie ne dépasse pas 10 000 caractères (idéalement, en théorie, la saisie doit être illimitée).
  • Votre code devrait se terminer assez rapidement. L'exemple donné ci-dessus ne devrait pas prendre plus d'une minute ou plus au pire. (Ceci est destiné à exclure la force brute.)
  • La notation est en octets.

Exemples

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

Bon codage!


Notez que cette question similaire est étroitement liée, même au point que celle-ci est un doublon. Cependant, le consensus à ce jour sur Meta est que l'ancien devrait être considéré comme un double de celui-ci.

El'endia Starman
la source
1
Je suis en désaccord avec votre note: c'est la même question qui pour les réponses existantes nécessite une simple transformation du format de sortie, et d'ailleurs toute réponse à cette question est automatiquement une réponse à la question précédente.
Peter Taylor du
@PeterTaylor: Je voudrais demander à nouveau que vous rouvriez cette question. La spécification dans celle-ci est meilleure (comme l'a dit Martin) et je veux voir des réponses plus récentes et meilleures, y compris des réponses Pyth et CJam. Je pense qu'il n'y a pas de mal à laisser les deux questions ouvertes car elles sont suffisamment différentes. Seuls deux des cinq utilisateurs qui ont posté sur cette question se sont rendus sur ce site récemment.
El'endia Starman,
@PeterTaylor: En outre, selon cette norme , je voudrais dire que je ne pense pas que les réponses puissent être copiées entre les questions et rester compétitives. Enfin, l'autre question a quatre ans . Ce serait bien d'avoir une nouvelle version.
El'endia Starman
Dans votre exemple de this question is about huffman coding, j'ai compté le nombre de bits pour 145 , pas 136.
TFeld
1
J'essayais vraiment de terminer ce défi dans Spoon , mais après 2 heures de réflexion, j'ai décidé qu'il serait préférable d'abandonner ...
Bassdrop Cumberwubwubwub

Réponses:

2

Pyth, 53 octets

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

Manifestation

Voici une version qui montre l'état interne, afin que vous puissiez voir l'encodage en cours de construction:

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

Manifestation

Copiez la sortie dans un environnement avec des lignes plus larges pour une image plus claire.

isaacg
la source
4

Python 2, 299 octets

Voici ma tentative de réponse.

Les codes Huffman sont différents des exemples donnés, mais devraient toujours être optimaux.

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
la source
2

Matlab, 116 octets

tabulatefait une table de fréquences. huffmandictprend une liste de symboles et de probabilités pour chaque symbole et calcule le code.

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
flawr
la source
2

Rubis, 189 180 octets

Travail en cours.

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

C'est une fonction anonyme; l'assigner à quelque chose, par exemple f, et l'appeler avec

f["some test string"]`

qui renvoie un hachage comme ceci:

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

Haskell, 227 octets

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

Exemple d'utilisation:

*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

Comment ça fonctionne:

ples appels fqui construisent la table Huffman sous la forme d'une liste de paires (de caractères, d'encodage), par exemple [ ('a',"0"), ('b',"1") ], trient la table en fonction de la longueur des encodages, formate chaque paire pour la sortie et joint les sauts de ligne entre les deux.

fvérifie d'abord la casse à une lettre et renvoie le tableau correspondant. Sinon, il trie la chaîne d'entrée et regroupe les séquences de caractères égaux (par exemple "ababa"-> ["aaa","bb"]) et les mappe en paires (sequence , [(char, "")]), (-> [ ("aaa", [('a',"")]), ("bb", [('b', "")])]. Le premier élément est utilisé pour garder une trace de la fréquence, le deuxième élément est une liste de paires d'un caractère et c'est le codage (qui est initialement vide). Ce sont toutes des tables Huffman à élément unique comme prévu par pet sont combinées par get h.

gtrie la liste des paires sur la longueur du premier élément, c'est-à-dire la fréquence et les appels h. hcombine les tables de Huffman des deux premiers éléments, en concaténant les fréquences et en mettant un 0( 1) devant chaque élément de la première (deuxième) table. Concatène les deux tables. Appelez à gnouveau, arrêtez quand il n'y a plus qu'un élément, jetez la partie fréquence et retournez la table Huffman complète.

nimi
la source
1

K (ngn / k) , 78 octets

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

Essayez-le en ligne!

renvoie une liste de chaînes à imprimer

h::0#'xcrée une liste vide pour chaque caractère (techniquement, il remodèle chaque caractère à la longueur 0). nous y stockerons les codes huffman inversés. nous utilisons ::au lieu de :pour assignation pour rendre hglobal afin qu'il soit visible dans les sous-fonctions.

.=x est une liste de listes - les indices de la chaîne regroupés par valeur de caractère

(#1_) est une fonction qui retourne vrai si la longueur de l'argument est> 1 (techniquement "longueur de 1 goutte de ...")

(#1_){... }/signifie: pendant que l'argument a une longueur> 1, continuez d'appliquer la fonction accolade

x@<#'x trier l'argument par longueur

0 2_ le couper en une tête à 2 éléments et une queue

{h[x],:!2;y,,,/x}mettre hà jour en ajoutant 0 et 1 aux indices contenus dans la tête; retourner la queue avec la tête comme un seul élément

(?,/'x,'" ",'|'$h)(?x)?>#'=xinverser chacun h, trier, unique, ajouter les caractères correspondants avant et formater correctement

ngn
la source
0

JavaScript (ES6) 279

Essentiellement, l'algorithme de base de Wikipedia. Je peux probablement faire mieux.

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

Plus lisible à l'intérieur de l'extrait ci-dessous

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
la source