编码霍夫曼!


13

否则他会怒气冲天,把你的房子炸毁!

那完全无关紧要。这个挑战实际上与霍夫曼编码有关。要点是给定文本中字符的频率被用来使其表示更短。换句话说,假设我们的字母是a通过z和空格。那是27个字符。它们中的每一个都可以仅用5位进行唯一编码,因为5位有足够的空间容纳32个字符。但是,在许多情况下(例如英语或一般语言),某些字符比其他字符更频繁。对于更频繁的字符,我们可以使用较少的位,对于较不频繁的字符,我们可以使用更多的位。如果做对了,总体上可以节省位数,并且仍然可以唯一地重建原始文本。

让我们以“这个问题与霍夫曼编码有关”为例。该文本的长度为37个字符,通常为37 * 8 = 296位,但是如果每个字符仅使用5位,则只有37 * 5 = 185位。记住这一点。

这是文本中每个字符及其频率的(排序)表,从最频繁到最不频繁(其中_代表空格)排序:

_ 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

相关的最佳编码可以是:

_ 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

应当立即清楚的是,与每个字符仅使用5位相比,这将是更好的编码。让我们找出更好的方法!

145位,而185 !这样可以节省40位,或仅节省20%以上!(当然,这是假定有关该结构的信息可用于解码。)这种编码是最佳的,因为通过更改任何字符的表示都不能丢掉更多的位。

任务

  • 用一个参数编写程序或函数,该参数可以...
  • 接受来自STDIN(或等效参数)的输入或作为单个参数。
  • 输出上述最佳霍夫曼编码,并按频率对字符进行排序(频率类内的顺序无关紧要)。
  • 您可以假定输入中的字符限制为ASCII范围32..126和换行符。
  • 您可能会假设输入的字符数不超过10,000个(理论上,理想情况下,输入应无限制)。
  • 您的代码应相当快地完成。上面给出的示例在最坏的情况下应该不会超过一分钟左右。(这是为了排除蛮力。)
  • 得分以字节为单位。

例子

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

编码愉快!


请注意,这个相似的问题是紧密相关的,甚至是重复的问题。但是,到目前为止,关于Meta 的共识是,较旧的应该被认为是Meta的副本。


1
我不同意您的注释:这是一个相同的问题,对于现有的答案,需要对输出格式进行简单的转换,此外,对该问题的任何答案都自动是对上一个问题的答案。
彼得·泰勒

@PeterTaylor:我想再次请你重新提出这个问题。这个规范比较好(如Martin所说),我希望看到更新更好的答案,包括Pyth和CJam答案。我认为让两个问题都保持开放性没有任何害处,因为它们之间的差异很大。最近在该问题上发布此问题的五个用户中只有两个。
El'endia Starman

@PeterTaylor:同样,按照这个标准,我想说的是,我认为答案不能复制在问题之间,也不能保持竞争力。最后,另一个问题是四岁。最好有一个新版本。
El'endia Starman

在你的例子 this question is about huffman coding,我计算的位数为145,而不是
136。– TF

1
我当时确实是在尝试完成Spoon的挑战,但是经过2个小时的
疯狂尝试

Answers:


2

Pyth,53个字节

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

示范

这是一个显示内部状态的版本,因此您可以看到正在构建的编码:

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

示范

将输出复制到线条较宽的环境中以获得更清晰的图片。


4

Python 2,299字节

这是我尝试的答案。

霍夫曼码与给出的示例不同,但仍应是最佳的。

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个字节

tabulate制作一个频率表。huffmandict获取每个符号的符号和概率的列表,然后计算代码。

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

红宝石, 189 180字节

工作正在进行中。

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

这是一个匿名函数;将其分配给某物,例如f,并使用进行调用

f["some test string"]`

返回如下所示的哈希值:

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

1

Haskell,227字节

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

用法示例:

*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

怎么运行的:

p调用f以(字符,编码)对列表的形式构建霍夫曼表,例如[ ('a',"0"), ('b',"1") ],按编码长度对表进行排序,格式化每对输出格式并在中间插入换行符。

f首先检查单个字母的大小写并返回相应的表。否则,它将对输入字符串进行排序,并将相等字符的序列分组(例如"ababa"-> ["aaa","bb"]),并将它们映射为对(sequence , [(char, "")])(-> [ ("aaa", [('a',"")]), ("bb", [('b', "")])]。第一个元素用于跟踪频率,第二个元素是一个字符对列表和它的编码(其最初是空的)。这些是如预期由所有单个元件霍夫曼表p和通过组合gh

g根据第一个元素的长度(即频率和calls)对对列表进行排序hh通过合并频率并将01)放在第一(第二)表的每个元素前面,从而合并前两个元素的霍夫曼表。连接两个表。g再次调用,在仅剩一个元素时停止,丢弃频率部分并返回完整的霍夫曼表。


1

K(ngn / k),78个字节

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

在线尝试!

返回要打印的字符串列表

h::0#'x为每个字符创建一个空列表(从技术上讲,它会将每个字符的形状调整为长度0)。我们将在此处存储反向的霍夫曼代码。我们使用::而不是:进行赋值来使h全局成为全局变量,以便在子功能中可见。

.=x 是列表的列表-按字符值分组的字符串的索引

(#1_) 是一个返回真值的函数,当参数的长度> 1(技术上是“一滴...的长度”)时

(#1_){... }/表示:当参数的长度> 1时,继续应用花括号功能

x@<#'x 按长度对参数进行排序

0 2_ 切成2个元素的头和尾

{h[x],:!2;y,,,/x}h通过将0和1附加到头部包含的索引中来进行更新;将尾部与头部作为单个元素返回

(?,/'x,'" ",'|'$h)(?x)?>#'=x反转h,排序,唯一,在相应字符之前加注,并且格式正确


0

JavaScript(ES6)279

本质上,是维基百科的基本算法。我可能会做得更好。

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

下面的代码段更具可读性

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>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.