Try it. Type some text and the demo builds a Huffman code from your letter frequencies: frequent letters get short bit-codes, rare ones get long codes. It shows the original size (fixed-width bits), the compressed size, and the ratio.
<p class="hint">Type text and the demo builds a <b>Huffman code</b> from your letter frequencies — frequent letters get short bit-codes, rare ones long. Watch it shrink below the raw 8-bits-per-character size, down toward the <b>entropy</b> floor.</p>
<textarea id="text" rows="3" class="ta"></textarea>
<div class="meters">
<div class="m"><span>Original (8 bits/char)</span><b id="orig">—</b></div>
<div class="m"><span>Huffman compressed</span><b id="comp" class="good">—</b></div>
<div class="m"><span>Saved</span><b id="ratio" class="good">—</b></div>
<div class="m"><span>Entropy floor</span><b id="ent">—</b></div>
</div>
<div class="codes" id="codes"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.ta { width: 100%; font: 600 14px ui-monospace, monospace; padding: .6rem; border: 1px solid #cdd9e2; border-radius: 8px; resize: vertical; }
.meters { display: flex; gap: .6rem; flex-wrap: wrap; margin: .8rem 0; }
.m { flex: 1; min-width: 120px; background: #f6f8fa; border: 1px solid #e6e9ee; border-radius: 10px; padding: .55rem .7rem; }
.m span { display: block; font-size: .76rem; color: #777; }
.m b { font: 800 18px ui-monospace, monospace; color: #1d3557; }
.good { color: #0a7d33 !important; }
.codes { display: flex; flex-wrap: wrap; gap: .35rem; }
.code { font: 700 12px ui-monospace, monospace; background: #f1f3f5; border-radius: 6px; padding: .2rem .45rem; color: #1d3557; }
.code .ch { color: #c0392b; }
.code .bits { color: #457b9d; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Huffman coding: build an optimal prefix code from symbol frequencies.
// Frequent letters -> short codes. Optimal for symbol coding (O(n log n));
// it approaches but never beats the Shannon entropy floor.
function huffman(s) {
const f = {}; for (const c of s) f[c] = (f[c] || 0) + 1;
const syms = Object.keys(f);
if (syms.length === 0) return { code: {}, bits: 0 };
if (syms.length === 1) { const code = {}; code[syms[0]] = '0'; return { code, bits: s.length }; }
let nodes = syms.map(c => ({ c, w: f[c] }));
while (nodes.length > 1) {
nodes.sort((a, b) => a.w - b.w);
const a = nodes.shift(), b = nodes.shift();
nodes.push({ w: a.w + b.w, l: a, r: b });
}
const code = {};
(function walk(n, p) { if (n.c !== undefined) { code[n.c] = p || '0'; return; } walk(n.l, p + '0'); walk(n.r, p + '1'); })(nodes[0], '');
let bits = 0; for (const c of s) bits += code[c].length;
return { code, bits };
}
function entropy(s) { const f = {}; for (const c of s) f[c] = (f[c] || 0) + 1; let h = 0; for (const k in f) { const p = f[k] / s.length; h -= p * Math.log2(p); } return h; }
const show = c => c === ' ' ? '␣' : c === '\n' ? '⏎' : c;
function run() {
const s = document.getElementById('text').value;
if (!s) { document.getElementById('codes').innerHTML = ''; ['orig','comp','ratio','ent'].forEach(id=>document.getElementById(id).textContent='—'); return; }
const r = huffman(s); const orig = s.length * 8;
document.getElementById('orig').textContent = fmt('%orig% bits', { orig: orig });
document.getElementById('comp').textContent = fmt('%bits% bits', { bits: r.bits });
document.getElementById('ratio').textContent = Math.max(0, Math.round((1 - r.bits / orig) * 100)) + '%';
document.getElementById('ent').textContent = fmt('%entropy% b/char', { entropy: entropy(s).toFixed(2) });
const entries = Object.entries(r.code).sort((a, b) => a[1].length - b[1].length);
document.getElementById('codes').innerHTML = entries.map(([ch, bits]) =>
'<span class="code"><span class="ch">' + show(ch) + '</span> <span class="bits">' + bits + '</span></span>').join('');
}
document.getElementById('text').value = 'abracadabra abracadabra';
document.getElementById('text').addEventListener('input', run);
run();
<p class="hint">Type text and the demo builds a <b>Huffman code</b> from your letter frequencies — frequent letters get short bit-codes, rare ones long. Watch it shrink below the raw 8-bits-per-character size, down toward the <b>entropy</b> floor.</p>
<textarea id="text" rows="3" class="ta"></textarea>
<div class="meters">
<div class="m"><span>Original (8 bits/char)</span><b id="orig">—</b></div>
<div class="m"><span>Huffman compressed</span><b id="comp" class="good">—</b></div>
<div class="m"><span>Saved</span><b id="ratio" class="good">—</b></div>
<div class="m"><span>Entropy floor</span><b id="ent">—</b></div>
</div>
<div class="codes" id="codes"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.ta { width: 100%; font: 600 14px ui-monospace, monospace; padding: .6rem; border: 1px solid #cdd9e2; border-radius: 8px; resize: vertical; }
.meters { display: flex; gap: .6rem; flex-wrap: wrap; margin: .8rem 0; }
.m { flex: 1; min-width: 120px; background: #f6f8fa; border: 1px solid #e6e9ee; border-radius: 10px; padding: .55rem .7rem; }
.m span { display: block; font-size: .76rem; color: #777; }
.m b { font: 800 18px ui-monospace, monospace; color: #1d3557; }
.good { color: #0a7d33 !important; }
.codes { display: flex; flex-wrap: wrap; gap: .35rem; }
.code { font: 700 12px ui-monospace, monospace; background: #f1f3f5; border-radius: 6px; padding: .2rem .45rem; color: #1d3557; }
.code .ch { color: #c0392b; }
.code .bits { color: #457b9d; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Huffman coding: build an optimal prefix code from symbol frequencies.
// Frequent letters -> short codes. Optimal for symbol coding (O(n log n));
// it approaches but never beats the Shannon entropy floor.
function huffman(s) {
const f = {}; for (const c of s) f[c] = (f[c] || 0) + 1;
const syms = Object.keys(f);
if (syms.length === 0) return { code: {}, bits: 0 };
if (syms.length === 1) { const code = {}; code[syms[0]] = '0'; return { code, bits: s.length }; }
let nodes = syms.map(c => ({ c, w: f[c] }));
while (nodes.length > 1) {
nodes.sort((a, b) => a.w - b.w);
const a = nodes.shift(), b = nodes.shift();
nodes.push({ w: a.w + b.w, l: a, r: b });
}
const code = {};
(function walk(n, p) { if (n.c !== undefined) { code[n.c] = p || '0'; return; } walk(n.l, p + '0'); walk(n.r, p + '1'); })(nodes[0], '');
let bits = 0; for (const c of s) bits += code[c].length;
return { code, bits };
}
function entropy(s) { const f = {}; for (const c of s) f[c] = (f[c] || 0) + 1; let h = 0; for (const k in f) { const p = f[k] / s.length; h -= p * Math.log2(p); } return h; }
const show = c => c === ' ' ? '␣' : c === '\n' ? '⏎' : c;
function run() {
const s = document.getElementById('text').value;
if (!s) { document.getElementById('codes').innerHTML = ''; ['orig','comp','ratio','ent'].forEach(id=>document.getElementById(id).textContent='—'); return; }
const r = huffman(s); const orig = s.length * 8;
document.getElementById('orig').textContent = fmt('%orig% bits', { orig: orig });
document.getElementById('comp').textContent = fmt('%bits% bits', { bits: r.bits });
document.getElementById('ratio').textContent = Math.max(0, Math.round((1 - r.bits / orig) * 100)) + '%';
document.getElementById('ent').textContent = fmt('%entropy% b/char', { entropy: entropy(s).toFixed(2) });
const entries = Object.entries(r.code).sort((a, b) => a[1].length - b[1].length);
document.getElementById('codes').innerHTML = entries.map(([ch, bits]) =>
'<span class="code"><span class="ch">' + show(ch) + '</span> <span class="bits">' + bits + '</span></span>').join('');
}
document.getElementById('text').value = 'abracadabra abracadabra';
document.getElementById('text').addEventListener('input', run);
run();
Repetitive or skewed text (lots of the same few letters) compresses dramatically; random, uniform text barely shrinks. Notice the compressed size hugging a floor — the Shannon entropy of your text. That floor is a real theorem: you can reach it, but for symbol coding you can't beat it.
Comments
Loading comments...