Кодуйте Хаффмана!


13

Або ж він буде гнати і пихнути і дути ваш будинок!

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

Візьмемо для прикладу "це питання про кодування Хаффмана". Цей текст має 37 символів, що нормально буде 37 * 8 = 296 бітів, хоча лише 37 * 5 = 185 біт, якщо ми використовуємо лише 5 біт для кожного символу. Майте це на увазі.

Ось таблиця (сортування) кожного символу та їх частоти в тексті, відсортована від найбільш часто до найменш частої (де _ означає пробіл):

_ 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 години мізерної страви я вирішив, що найкраще відмовитись ...
Bassdrop Cumberwubwubwub

Відповіді:


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

Матлаб, 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_) - це функція, яка повертає правду iff, якщо довжина аргументу> 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.