Type a short word below. The demo appends an end-marker $, builds every rotation of the string, sorts them into a grid, and reads off the last column in red — that string is the Burrows-Wheeler transform. Notice how identical letters bunch together.
<p class="hint">Type a short word, then press <b>Transform</b>. The demo builds every rotation of the text, sorts them, and reads off the <b>last column</b> — that string is the Burrows-Wheeler transform. Press <b>Invert</b> to rebuild the original from the last column alone. The transform is perfectly reversible.</p>
<div class="row">
<input id="txt" type="text" maxlength="12" value="banana" autocomplete="off" spellcheck="false" />
<button id="run" type="button">Transform</button>
<button id="inv" type="button" class="ghost">Invert</button>
</div>
<div class="out" id="bwtline"></div>
<div class="matrix" id="matrix"></div>
<div class="status" id="status">A "$" end-marker is appended so the transform is reversible.</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 .7rem; line-height: 1.45; }
.row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .6rem; }
input { font: 600 15px ui-monospace, monospace; padding: .45rem .6rem; border: 1px solid #adb1b8;
border-radius: 8px; width: 9rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.out { font: 700 16px ui-monospace, monospace; color: #1d3557; min-height: 1.4em; margin: .3rem 0 .6rem; }
.out b { color: #e63946; }
.matrix { display: inline-grid; gap: 2px; margin: .2rem 0; }
.mrow { display: flex; gap: 2px; }
.ch { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; background: #e8eef3; color: #1d3557;
border: 1px solid #cdd9e3; border-radius: 5px; }
.ch.last { background: #e63946; border-color: #c92f3c; color: #fff; }
.ch.first { background: #cfe6d6; border-color: #9fcfb0; color: #0a7d33; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; color: #444; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Burrows-Wheeler Transform (Burrows & Wheeler, 1994).
// Forward: append an end-marker "$" (smallest character), list every rotation of the string, sort them, and output the LAST column. Identical characters tend to cluster together, which makes the result far more compressible.
// Inverse: from the last column alone you can rebuild the whole sorted matrix (LF-mapping / repeated-prepend), recovering the original exactly.
const END = '
Then press Invert. Working from the last column alone, the demo rebuilds the sorted grid one column at a time and recovers your original word — exactly. Try banana (its transform is annb$aa) or mississippi, and watch the repeated letters cluster into runs that a compressor loves.
;
function rotations(s){
const r = [];
for (let i = 0; i < s.length; i++) r.push(s.slice(i) + s.slice(0, i));
return r;
}
function bwt(s){
const m = rotations(s + END).sort();
return { matrix: m, last: m.map(r => r[r.length - 1]).join('') };
}
// Inverse by repeated prepend-and-sort: prepend the last column, sort, repeat n times; the row ending in "$" is the original (with the marker).
function ibwt(last){
const n = last.length;
let table = Array.from({ length: n }, () => '');
for (let k = 0; k < n; k++){
for (let i = 0; i < n; i++) table[i] = last[i] + table[i];
table.sort();
}
const row = table.find(r => r[r.length - 1] === END);
return row.slice(0, -1); // strip the "$"
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls || ''); }
function clean(){ return (document.getElementById('txt').value || '').replace(/\$/g, '').slice(0, 12); }
function showMatrix(matrix){
const box = document.getElementById('matrix'); box.innerHTML = '';
matrix.forEach(rowStr => {
const row = document.createElement('div'); row.className = 'mrow';
for (let i = 0; i < rowStr.length; i++){
const c = document.createElement('div');
c.className = 'ch' + (i === 0 ? ' first' : '') + (i === rowStr.length - 1 ? ' last' : '');
c.textContent = rowStr[i];
row.appendChild(c);
}
box.appendChild(row);
});
}
function transform(){
const s = clean();
if (!s){ setStatus('Type at least one character.', 'bad'); return; }
const { matrix, last } = bwt(s);
showMatrix(matrix);
document.getElementById('bwtline').innerHTML = fmt('BWT (last column): <b>%last%</b>', { last: last });
setStatus(fmt('Sorted %length% rotations. Green = first column, red = last column (the transform).', { length: matrix.length }), 'ok');
console.log('BWT("' + s + '$") =', last);
}
function invert(){
const s = clean();
if (!s){ setStatus('Type at least one character.', 'bad'); return; }
const last = bwt(s).last;
const back = ibwt(last);
const ok = back === s;
setStatus(fmt('%v% Inverted "%last%" → "%back%"%v2%', { v: ok ? '✓' : '✗', last: last, back: back, v2: ok ? ' — matches the original!' : ' (mismatch)' }), ok ? 'ok' : 'bad');
console.log('iBWT("' + last + '") =', back, '| matches:', ok);
}
document.getElementById('run').addEventListener('click', transform);
document.getElementById('inv').addEventListener('click', invert);
document.getElementById('txt').addEventListener('keydown', e => { if (e.key === 'Enter') transform(); });
transform();
<p class="hint">Type a short word, then press <b>Transform</b>. The demo builds every rotation of the text, sorts them, and reads off the <b>last column</b> — that string is the Burrows-Wheeler transform. Press <b>Invert</b> to rebuild the original from the last column alone. The transform is perfectly reversible.</p>
<div class="row">
<input id="txt" type="text" maxlength="12" value="banana" autocomplete="off" spellcheck="false" />
<button id="run" type="button">Transform</button>
<button id="inv" type="button" class="ghost">Invert</button>
</div>
<div class="out" id="bwtline"></div>
<div class="matrix" id="matrix"></div>
<div class="status" id="status">A "$" end-marker is appended so the transform is reversible.</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 .7rem; line-height: 1.45; }
.row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .6rem; }
input { font: 600 15px ui-monospace, monospace; padding: .45rem .6rem; border: 1px solid #adb1b8;
border-radius: 8px; width: 9rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.out { font: 700 16px ui-monospace, monospace; color: #1d3557; min-height: 1.4em; margin: .3rem 0 .6rem; }
.out b { color: #e63946; }
.matrix { display: inline-grid; gap: 2px; margin: .2rem 0; }
.mrow { display: flex; gap: 2px; }
.ch { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; background: #e8eef3; color: #1d3557;
border: 1px solid #cdd9e3; border-radius: 5px; }
.ch.last { background: #e63946; border-color: #c92f3c; color: #fff; }
.ch.first { background: #cfe6d6; border-color: #9fcfb0; color: #0a7d33; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; color: #444; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Burrows-Wheeler Transform (Burrows & Wheeler, 1994).
// Forward: append an end-marker "$" (smallest character), list every rotation of the string, sort them, and output the LAST column. Identical characters tend to cluster together, which makes the result far more compressible.
// Inverse: from the last column alone you can rebuild the whole sorted matrix (LF-mapping / repeated-prepend), recovering the original exactly.
const END = '
Then press Invert. Working from the last column alone, the demo rebuilds the sorted grid one column at a time and recovers your original word — exactly. Try banana (its transform is annb$aa) or mississippi, and watch the repeated letters cluster into runs that a compressor loves.
;
function rotations(s){
const r = [];
for (let i = 0; i < s.length; i++) r.push(s.slice(i) + s.slice(0, i));
return r;
}
function bwt(s){
const m = rotations(s + END).sort();
return { matrix: m, last: m.map(r => r[r.length - 1]).join('') };
}
// Inverse by repeated prepend-and-sort: prepend the last column, sort, repeat n times; the row ending in "$" is the original (with the marker).
function ibwt(last){
const n = last.length;
let table = Array.from({ length: n }, () => '');
for (let k = 0; k < n; k++){
for (let i = 0; i < n; i++) table[i] = last[i] + table[i];
table.sort();
}
const row = table.find(r => r[r.length - 1] === END);
return row.slice(0, -1); // strip the "$"
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls || ''); }
function clean(){ return (document.getElementById('txt').value || '').replace(/\$/g, '').slice(0, 12); }
function showMatrix(matrix){
const box = document.getElementById('matrix'); box.innerHTML = '';
matrix.forEach(rowStr => {
const row = document.createElement('div'); row.className = 'mrow';
for (let i = 0; i < rowStr.length; i++){
const c = document.createElement('div');
c.className = 'ch' + (i === 0 ? ' first' : '') + (i === rowStr.length - 1 ? ' last' : '');
c.textContent = rowStr[i];
row.appendChild(c);
}
box.appendChild(row);
});
}
function transform(){
const s = clean();
if (!s){ setStatus('Type at least one character.', 'bad'); return; }
const { matrix, last } = bwt(s);
showMatrix(matrix);
document.getElementById('bwtline').innerHTML = fmt('BWT (last column): <b>%last%</b>', { last: last });
setStatus(fmt('Sorted %length% rotations. Green = first column, red = last column (the transform).', { length: matrix.length }), 'ok');
console.log('BWT("' + s + '$") =', last);
}
function invert(){
const s = clean();
if (!s){ setStatus('Type at least one character.', 'bad'); return; }
const last = bwt(s).last;
const back = ibwt(last);
const ok = back === s;
setStatus(fmt('%v% Inverted "%last%" → "%back%"%v2%', { v: ok ? '✓' : '✗', last: last, back: back, v2: ok ? ' — matches the original!' : ' (mismatch)' }), ok ? 'ok' : 'bad');
console.log('iBWT("' + last + '") =', back, '| matches:', ok);
}
document.getElementById('run').addEventListener('click', transform);
document.getElementById('inv').addEventListener('click', invert);
document.getElementById('txt').addEventListener('keydown', e => { if (e.key === 'Enter') transform(); });
transform();
Then press Invert. Working from the last column alone, the demo rebuilds the sorted grid one column at a time and recovers your original word — exactly. Try banana (its transform is annb$aa) or mississippi, and watch the repeated letters cluster into runs that a compressor loves.
Comments
Loading comments...