The demo below lets you enter any string and watch a simple run-length encoder compress it. The compressed length gives an upper bound on the Kolmogorov complexity — proof that a short program (the decompressor + the compressed data) can reproduce the original.
<p class="hint">Enter any string and see how a run-length encoder compresses it. The compressed length is an <b>upper bound</b> on Kolmogorov complexity — it proves a short program can reproduce the string. Try structured vs random strings.</p>
<div class="controls">
<input id="strInput" type="text" maxlength="120" placeholder="e.g. ABABABABABABABABAB" autocomplete="off" spellcheck="false" />
<button id="compressBtn" type="button">Compress</button>
</div>
<div class="presets">
<span class="label">Presets:</span>
<button class="preset" data-val="AAAAAAAAAAAAAAAAAAAAAAAAA">25×A</button>
<button class="preset" data-val="ABABABABABABABABABABABABAB">ABAB×12</button>
<button class="preset" data-val="AAABBBCCCDDDEEEFFFGGGHHH">ABC runs</button>
<button class="preset" data-val="BDKFQMXZWRJVNTHPLCSGYBEA">random-like</button>
</div>
<div id="result" class="result hidden">
<div class="bar-row">
<span class="bar-label">Original</span>
<div class="bar-wrap"><div id="origBar" class="bar orig"></div></div>
<span class="bar-num" id="origLen">0</span>
</div>
<div class="bar-row">
<span class="bar-label">Compressed</span>
<div class="bar-wrap"><div id="compBar" class="bar comp"></div></div>
<span class="bar-num" id="compLen">0</span>
</div>
<div class="detail" id="detail"></div>
<div class="verdict" id="verdict"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; gap: .5rem; margin-bottom: .5rem; }
#strInput { flex: 1; font: 14px ui-monospace, monospace; padding: .4rem .6rem;
border: 1px solid #adb1b8; border-radius: 8px; outline: none; }
#strInput:focus { border-color: #1d3557; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; white-space: nowrap; }
button:hover { background: #162a45; }
.presets { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-bottom: .9rem; }
.label { font-size: .8rem; color: #666; }
.preset { font: 600 11px ui-monospace, monospace; padding: .28rem .55rem;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3;
border-radius: 6px; cursor: pointer; }
.preset:hover { background: #d0dce8; }
.result { margin-top: .4rem; }
.result.hidden { display: none; }
.bar-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .45rem; }
.bar-label { width: 88px; font-size: .82rem; color: #555; flex-shrink: 0; }
.bar-wrap { flex: 1; background: #e8eef3; border-radius: 5px; overflow: hidden; height: 18px; }
.bar { height: 18px; border-radius: 5px; transition: width .4s ease; min-width: 4px; }
.bar.orig { background: #1d3557; }
.bar.comp { background: #2a9d8f; }
.bar-num { width: 36px; font: 700 13px ui-monospace, monospace; color: #333; text-align: right; flex-shrink: 0; }
.detail { font: 13px ui-monospace, monospace; background: #f4f6f8; border: 1px solid #dde2e8;
border-radius: 8px; padding: .5rem .7rem; margin-top: .5rem; white-space: pre-wrap; word-break: break-all; color: #333; }
.verdict { margin-top: .6rem; font-size: .93rem; font-weight: 600; padding: .45rem .7rem;
border-radius: 8px; line-height: 1.4; }
.verdict.low { background: #d4edda; color: #155724; }
.verdict.med { background: #fff3cd; color: #856404; }
.verdict.high { background: #f8d7da; color: #721c24; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Run-length encoding: compress runs of identical characters.
// Output format: count (if >1) then char. Digit chars are escaped with ’~’
// so the decompressor can distinguish them from run-count digits.
// E.g. "AAABBC" -> "3A2BC", "111" -> "3~1", "a1b" -> "a~1b"
// This gives an UPPER BOUND on Kolmogorov complexity K(s):
// K(s) <= |compressed| + |decompressor constant|
// Highly structured strings compress a lot; truly random strings expand (because digit escaping adds overhead), proving no short description exists.
function rleCompress(s) {
if (!s.length) return '';
let out = '';
let i = 0;
while (i < s.length) {
let j = i;
while (j < s.length && s[j] === s[i]) j++;
const run = j - i;
const ch = s[i];
const isDigit = ch >= '0' && ch <= '9';
const encoded = isDigit ? ('~' + ch) : ch;
out += (run > 1 ? run : '') + encoded;
i = j;
}
return out;
}
function rleDecompress(c) {
let out = '';
let i = 0;
while (i < c.length) {
let numStr = '';
while (i < c.length && c[i] >= '0' && c[i] <= '9') { numStr += c[i]; i++; }
if (i >= c.length) break;
let ch;
if (c[i] === '~') { i++; ch = c[i]; i++; }
else { ch = c[i]; i++; }
const count = numStr ? parseInt(numStr) : 1;
out += ch.repeat(count);
}
return out;
}
function compress() {
const raw = document.getElementById('strInput').value.trim();
if (!raw) return;
const comp = rleCompress(raw);
const origLen = raw.length;
const compLen = comp.length;
const ratio = compLen / origLen;
document.getElementById('result').classList.remove('hidden');
document.getElementById('origLen').textContent = origLen;
document.getElementById('compLen').textContent = compLen;
const pct = Math.min(150, Math.round(ratio * 100));
document.getElementById('origBar').style.width = '100%';
document.getElementById('compBar').style.width = Math.min(100, pct) + '%';
const saved = origLen - compLen;
const savedPct = Math.round((1 - ratio) * 100);
document.getElementById('detail').textContent =
fmt(`Original : %raw%
RLE output: %comp%
%v%`, { raw: raw, comp: comp, v: saved > 0
? fmt(`Saved : %saved% chars (%savedPct%% reduction)`, { saved: saved, savedPct: savedPct })
: fmt(`Expanded : %expanded% chars (no runs found; digit escaping adds overhead)`, { expanded: -saved }) });
const vEl = document.getElementById('verdict');
let cls, msg;
if (ratio <= 0.5) {
cls = 'low';
msg = fmt(`K(s) ≤ %compLen% chars — highly compressible. This string has a short description: its true complexity is much less than its length.`, { compLen: compLen });
} else if (ratio < 1.0) {
cls = 'med';
msg = fmt(`K(s) ≤ %compLen% chars — partially compressible. Some structure exists, but the string resists full compression.`, { compLen: compLen });
} else {
cls = 'high';
msg = fmt(`K(s) ≤ %origLen% chars (original length) — no compression found. This string resists run-length encoding: its complexity is at least as large as its length, a hallmark of randomness.`, { origLen: origLen });
}
vEl.className = 'verdict ' + cls;
vEl.textContent = msg;
// Verify roundtrip correctness
const recovered = rleDecompress(comp);
console.assert(recovered === raw, `RLE roundtrip failed!`);
console.log(`Input:`, raw, '| Compressed:', comp, '| Ratio:', ratio.toFixed(3));
}
document.getElementById('compressBtn').addEventListener('click', compress);
document.getElementById('strInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') compress(); });
document.querySelectorAll('.preset').forEach(function(btn) {
btn.addEventListener('click', function() {
document.getElementById('strInput').value = btn.dataset.val;
compress();
});
});
// Run on a default string immediately
document.getElementById('strInput').value = 'ABABABABABABABABABABABABAB';
compress();
<p class="hint">Enter any string and see how a run-length encoder compresses it. The compressed length is an <b>upper bound</b> on Kolmogorov complexity — it proves a short program can reproduce the string. Try structured vs random strings.</p>
<div class="controls">
<input id="strInput" type="text" maxlength="120" placeholder="e.g. ABABABABABABABABAB" autocomplete="off" spellcheck="false" />
<button id="compressBtn" type="button">Compress</button>
</div>
<div class="presets">
<span class="label">Presets:</span>
<button class="preset" data-val="AAAAAAAAAAAAAAAAAAAAAAAAA">25×A</button>
<button class="preset" data-val="ABABABABABABABABABABABABAB">ABAB×12</button>
<button class="preset" data-val="AAABBBCCCDDDEEEFFFGGGHHH">ABC runs</button>
<button class="preset" data-val="BDKFQMXZWRJVNTHPLCSGYBEA">random-like</button>
</div>
<div id="result" class="result hidden">
<div class="bar-row">
<span class="bar-label">Original</span>
<div class="bar-wrap"><div id="origBar" class="bar orig"></div></div>
<span class="bar-num" id="origLen">0</span>
</div>
<div class="bar-row">
<span class="bar-label">Compressed</span>
<div class="bar-wrap"><div id="compBar" class="bar comp"></div></div>
<span class="bar-num" id="compLen">0</span>
</div>
<div class="detail" id="detail"></div>
<div class="verdict" id="verdict"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; gap: .5rem; margin-bottom: .5rem; }
#strInput { flex: 1; font: 14px ui-monospace, monospace; padding: .4rem .6rem;
border: 1px solid #adb1b8; border-radius: 8px; outline: none; }
#strInput:focus { border-color: #1d3557; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; white-space: nowrap; }
button:hover { background: #162a45; }
.presets { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-bottom: .9rem; }
.label { font-size: .8rem; color: #666; }
.preset { font: 600 11px ui-monospace, monospace; padding: .28rem .55rem;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3;
border-radius: 6px; cursor: pointer; }
.preset:hover { background: #d0dce8; }
.result { margin-top: .4rem; }
.result.hidden { display: none; }
.bar-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .45rem; }
.bar-label { width: 88px; font-size: .82rem; color: #555; flex-shrink: 0; }
.bar-wrap { flex: 1; background: #e8eef3; border-radius: 5px; overflow: hidden; height: 18px; }
.bar { height: 18px; border-radius: 5px; transition: width .4s ease; min-width: 4px; }
.bar.orig { background: #1d3557; }
.bar.comp { background: #2a9d8f; }
.bar-num { width: 36px; font: 700 13px ui-monospace, monospace; color: #333; text-align: right; flex-shrink: 0; }
.detail { font: 13px ui-monospace, monospace; background: #f4f6f8; border: 1px solid #dde2e8;
border-radius: 8px; padding: .5rem .7rem; margin-top: .5rem; white-space: pre-wrap; word-break: break-all; color: #333; }
.verdict { margin-top: .6rem; font-size: .93rem; font-weight: 600; padding: .45rem .7rem;
border-radius: 8px; line-height: 1.4; }
.verdict.low { background: #d4edda; color: #155724; }
.verdict.med { background: #fff3cd; color: #856404; }
.verdict.high { background: #f8d7da; color: #721c24; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Run-length encoding: compress runs of identical characters.
// Output format: count (if >1) then char. Digit chars are escaped with ’~’
// so the decompressor can distinguish them from run-count digits.
// E.g. "AAABBC" -> "3A2BC", "111" -> "3~1", "a1b" -> "a~1b"
// This gives an UPPER BOUND on Kolmogorov complexity K(s):
// K(s) <= |compressed| + |decompressor constant|
// Highly structured strings compress a lot; truly random strings expand (because digit escaping adds overhead), proving no short description exists.
function rleCompress(s) {
if (!s.length) return '';
let out = '';
let i = 0;
while (i < s.length) {
let j = i;
while (j < s.length && s[j] === s[i]) j++;
const run = j - i;
const ch = s[i];
const isDigit = ch >= '0' && ch <= '9';
const encoded = isDigit ? ('~' + ch) : ch;
out += (run > 1 ? run : '') + encoded;
i = j;
}
return out;
}
function rleDecompress(c) {
let out = '';
let i = 0;
while (i < c.length) {
let numStr = '';
while (i < c.length && c[i] >= '0' && c[i] <= '9') { numStr += c[i]; i++; }
if (i >= c.length) break;
let ch;
if (c[i] === '~') { i++; ch = c[i]; i++; }
else { ch = c[i]; i++; }
const count = numStr ? parseInt(numStr) : 1;
out += ch.repeat(count);
}
return out;
}
function compress() {
const raw = document.getElementById('strInput').value.trim();
if (!raw) return;
const comp = rleCompress(raw);
const origLen = raw.length;
const compLen = comp.length;
const ratio = compLen / origLen;
document.getElementById('result').classList.remove('hidden');
document.getElementById('origLen').textContent = origLen;
document.getElementById('compLen').textContent = compLen;
const pct = Math.min(150, Math.round(ratio * 100));
document.getElementById('origBar').style.width = '100%';
document.getElementById('compBar').style.width = Math.min(100, pct) + '%';
const saved = origLen - compLen;
const savedPct = Math.round((1 - ratio) * 100);
document.getElementById('detail').textContent =
fmt(`Original : %raw%
RLE output: %comp%
%v%`, { raw: raw, comp: comp, v: saved > 0
? fmt(`Saved : %saved% chars (%savedPct%% reduction)`, { saved: saved, savedPct: savedPct })
: fmt(`Expanded : %expanded% chars (no runs found; digit escaping adds overhead)`, { expanded: -saved }) });
const vEl = document.getElementById('verdict');
let cls, msg;
if (ratio <= 0.5) {
cls = 'low';
msg = fmt(`K(s) ≤ %compLen% chars — highly compressible. This string has a short description: its true complexity is much less than its length.`, { compLen: compLen });
} else if (ratio < 1.0) {
cls = 'med';
msg = fmt(`K(s) ≤ %compLen% chars — partially compressible. Some structure exists, but the string resists full compression.`, { compLen: compLen });
} else {
cls = 'high';
msg = fmt(`K(s) ≤ %origLen% chars (original length) — no compression found. This string resists run-length encoding: its complexity is at least as large as its length, a hallmark of randomness.`, { origLen: origLen });
}
vEl.className = 'verdict ' + cls;
vEl.textContent = msg;
// Verify roundtrip correctness
const recovered = rleDecompress(comp);
console.assert(recovered === raw, `RLE roundtrip failed!`);
console.log(`Input:`, raw, '| Compressed:', comp, '| Ratio:', ratio.toFixed(3));
}
document.getElementById('compressBtn').addEventListener('click', compress);
document.getElementById('strInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') compress(); });
document.querySelectorAll('.preset').forEach(function(btn) {
btn.addEventListener('click', function() {
document.getElementById('strInput').value = btn.dataset.val;
compress();
});
});
// Run on a default string immediately
document.getElementById('strInput').value = 'ABABABABABABABABABABABABAB';
compress();
Notice what happens. A highly structured string like ABABABABAB compresses dramatically — its true complexity is low. A string of digits of π compresses a bit, because there's a short program that generates π. A genuinely random-looking string resists compression entirely: the best upper bound we can find is nearly equal to the string's own length.
The key insight: we can only ever upper-bound K(s). Any compression algorithm that produces a shorter description proves K(s) is at most that length. But proving K(s) is exactly some value — proving no shorter program exists — would require searching all programs, which is uncomputable.
Comments
Loading comments...