Код Хаффмана!


13

Или же он будет пыхтеть, пыхтеть и взрывать твой дом!

Это было совершенно неактуально. Эта проблема на самом деле о кодировании Хаффмана . Суть в том, что частота символов в данном тексте используется, чтобы сделать его представление короче. Другими словами, скажем, что наш алфавит aсквозной zи пробел. Это 27 символов. Каждый из них может быть уникально закодирован всего в 5 битах, потому что 5 битам достаточно места для 32 символов. Однако во многих ситуациях (например, в английском или в других языках) некоторые символы встречаются чаще, чем другие. Мы можем использовать меньше битов для более частых символов и (возможно) больше битов для менее частых символов. Если все сделано правильно, то количество битов в целом уменьшится, а исходный текст все равно можно будет восстановить уникальным образом.

Давайте возьмем «этот вопрос о кодировании Хаффмана» в качестве примера. Этот текст имеет длину 37 символов, что обычно составляет 37 * 8 = 296 бит, хотя только 37 * 5 = 185 бит, если мы используем только 5 бит для каждого символа. Запомни.

Вот таблица (sorta) каждого символа и их частоты в тексте, отсортированная от наиболее частой к наименее (где _ обозначает пробел):

_ 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

Удачного кодирования!


Обратите внимание, что этот похожий вопрос тесно связан даже с тем, что этот вопрос является дубликатом. Тем не менее, единый консенсус в отношении Меты заключается в том, что более старый следует считать его дубликатом.


1
Я не согласен с вашей запиской: это тот же вопрос, который для существующих ответов требует простого преобразования выходного формата, и, кроме того, любой ответ на этот вопрос автоматически является ответом на предыдущий вопрос.
Питер Тейлор

@PeterTaylor: Я бы хотел еще раз подать прошение, чтобы вы снова открыли этот вопрос. Спецификация в этом лучше (как сказал Мартин), и я хочу видеть новые, лучшие ответы, включая ответы Pyth и CJam. Я думаю, что не стоит оставлять оба вопроса открытыми, потому что они достаточно разные. Только два из пяти пользователей, которые разместили этот вопрос, недавно были на этом сайте.
El'endia Starman

@PeterTaylor: Кроме того, следуя этому стандарту , я хотел бы сказать, что я не думаю, что ответы можно копировать между вопросами и оставаться конкурентоспособными. Наконец, другому вопросу четыре года . Было бы хорошо иметь свежую версию.
El'endia Starman

В вашем примере this question is about huffman codingя посчитал число битов равным 145 , а не 136.
TFeld

1
Я действительно пытался выполнить это задание в Spoon , но после 2 часов
мозгового лукавства

Ответы:


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и объединяются с помощью gи h.

gсортирует список пар по длине первого элемента, то есть по частоте и по вызовам h. hобъединяет таблицы Хаффмана первых двух элементов, объединяя частоты и помещая 0( 1) перед каждым элементом первой (второй) таблицы. Объединить обе таблицы. Вызовите gснова, остановитесь, когда останется один элемент, отбросьте частотную часть и верните полную таблицу Хаффмана.


1

К (нгн / к) , 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_){... }/означает: пока аргумент имеет длину> 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>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.