The demo below sorts a shuffled list of three-digit numbers using LSD radix sort. Press Step to execute one digit pass (units → tens → hundreds) and watch the bars lock into place one digit at a time. Press Reset to shuffle and start again.
<p class="hint">A shuffled list of 3-digit numbers sorted by LSD radix sort. Each <b>Step</b> runs one stable counting-sort pass on a single digit position (units → tens → hundreds). Watch each pass lock one digit into place.</p>
<div id="pass-label" class="pass-label">Pass: <span id="pass-name">–</span></div>
<div id="bars" class="bars"></div>
<div id="digits-row" class="digits-row"></div>
<div class="status" id="status">Press Step to begin.</div>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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.45; }
.pass-label { font-size: .85rem; font-weight: 600; color: #1d3557; margin-bottom: .4rem; min-height: 1.2em; }
.bars { display: flex; align-items: flex-end; gap: 5px; height: 180px; padding: 0 2px; }
.bar-wrap { display: flex; flex-direction: column; align-items: center; flex: 1; }
.bar { width: 100%; border-radius: 4px 4px 0 0; transition: height .35s, background .35s; }
.bar.highlighted { background: #e63946 !important; }
.bar-val { font-size: .72rem; font-weight: 700; color: #1d3557; margin-top: 3px; }
.digits-row { display: flex; gap: 5px; margin-top: 4px; padding: 0 2px; }
.digit-badge { flex: 1; text-align: center; font-size: .7rem; padding: 2px 0; border-radius: 4px;
font-weight: 700; color: #fff; min-height: 1.4em; }
.status { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// LSD Radix Sort demo
// Algorithm: for each digit position d = 0 (units), 1 (tens), 2 (hundreds):
// 1. Stable counting sort on digit d of each number.
// 2. Output array is the sorted-by-digit-d version (stable = prior order preserved).
// After 3 passes the array is fully sorted.
const DIGIT_NAMES = ['Units (ones)', 'Tens', 'Hundreds'];
const DIGIT_COLORS = ['#457b9d', '#2a9d8f', '#e76f51'];
const PALETTE = ['#a8c8e8','#90c0b8','#f4a87a','#b5d5f5','#95d5cc','#f7c09a',
'#7bafd4','#5bbcb0','#e88a56','#c5e0f8','#a8e0d8','#f9ceaa'];
function getDigit(n, pos) { return Math.floor(n / Math.pow(10, pos)) % 10; }
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// Generate a list of 12 distinct 3-digit numbers
function makeList() {
const base = [123,245,318,472,519,634,721,856,963,187,342,608];
return shuffle([...base]);
}
let arr = makeList();
let pass = 0; // 0=units, 1=tens, 2=hundreds; 3=done
function countingSortByDigit(a, pos) {
const count = new Array(10).fill(0);
a.forEach(v => count[getDigit(v, pos)]++);
// prefix sums (cumulative)
for (let i = 1; i < 10; i++) count[i] += count[i - 1];
const output = new Array(a.length);
// stable: iterate right to left
for (let i = a.length - 1; i >= 0; i--) {
const d = getDigit(a[i], pos);
output[--count[d]] = a[i];
}
return output;
}
// Verify sort correctness (used in node test)
function isSorted(a) {
for (let i = 1; i < a.length; i++) if (a[i] < a[i - 1]) return false;
return true;
}
const maxVal = 999;
function render(highlight) {
const barsEl = document.getElementById('bars');
const digitsEl = document.getElementById('digits-row');
barsEl.innerHTML = '';
digitsEl.innerHTML = '';
arr.forEach((v, i) => {
const wrap = document.createElement('div');
wrap.className = 'bar-wrap';
const bar = document.createElement('div');
bar.className = 'bar' + (highlight && highlight.has(i) ? ' highlighted' : '');
bar.style.height = Math.round((v / maxVal) * 160 + 12) + 'px';
bar.style.background = highlight && highlight.has(i) ? '' : PALETTE[i % PALETTE.length];
const label = document.createElement('div');
label.className = 'bar-val';
label.textContent = v;
wrap.appendChild(bar);
wrap.appendChild(label);
barsEl.appendChild(wrap);
// digit badge showing the active digit
const badge = document.createElement('div');
badge.className = 'digit-badge';
if (pass < 3) {
badge.style.background = DIGIT_COLORS[pass];
badge.textContent = getDigit(v, pass);
} else {
badge.style.background = '#0a7d33';
badge.textContent = '✓';
}
digitsEl.appendChild(badge);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + (cls || '');
}
function updatePassLabel() {
document.getElementById('pass-name').textContent =
pass < 3 ? DIGIT_NAMES[pass] : 'Done';
}
function step() {
if (pass >= 3) return;
arr = countingSortByDigit(arr, pass);
pass++;
render();
updatePassLabel();
if (pass === 3) {
setStatus('All 3 passes complete — array is sorted!', 'ok');
document.getElementById('step').disabled = true;
console.log('Sorted:', arr.join(', '), '| isSorted:', isSorted(arr));
} else {
setStatus(fmt('Pass %pass% done. Next: sort by %v%.', { pass: pass, v: DIGIT_NAMES[pass] }));
}
}
function reset() {
arr = makeList();
pass = 0;
document.getElementById('step').disabled = false;
setStatus('Press Step to begin.');
updatePassLabel();
render();
console.log('Reset. Initial:', arr.join(', '));
}
document.getElementById('step').addEventListener('click', step);
document.getElementById('reset').addEventListener('click', reset);
render();
updatePassLabel();
<p class="hint">A shuffled list of 3-digit numbers sorted by LSD radix sort. Each <b>Step</b> runs one stable counting-sort pass on a single digit position (units → tens → hundreds). Watch each pass lock one digit into place.</p>
<div id="pass-label" class="pass-label">Pass: <span id="pass-name">–</span></div>
<div id="bars" class="bars"></div>
<div id="digits-row" class="digits-row"></div>
<div class="status" id="status">Press Step to begin.</div>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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.45; }
.pass-label { font-size: .85rem; font-weight: 600; color: #1d3557; margin-bottom: .4rem; min-height: 1.2em; }
.bars { display: flex; align-items: flex-end; gap: 5px; height: 180px; padding: 0 2px; }
.bar-wrap { display: flex; flex-direction: column; align-items: center; flex: 1; }
.bar { width: 100%; border-radius: 4px 4px 0 0; transition: height .35s, background .35s; }
.bar.highlighted { background: #e63946 !important; }
.bar-val { font-size: .72rem; font-weight: 700; color: #1d3557; margin-top: 3px; }
.digits-row { display: flex; gap: 5px; margin-top: 4px; padding: 0 2px; }
.digit-badge { flex: 1; text-align: center; font-size: .7rem; padding: 2px 0; border-radius: 4px;
font-weight: 700; color: #fff; min-height: 1.4em; }
.status { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// LSD Radix Sort demo
// Algorithm: for each digit position d = 0 (units), 1 (tens), 2 (hundreds):
// 1. Stable counting sort on digit d of each number.
// 2. Output array is the sorted-by-digit-d version (stable = prior order preserved).
// After 3 passes the array is fully sorted.
const DIGIT_NAMES = ['Units (ones)', 'Tens', 'Hundreds'];
const DIGIT_COLORS = ['#457b9d', '#2a9d8f', '#e76f51'];
const PALETTE = ['#a8c8e8','#90c0b8','#f4a87a','#b5d5f5','#95d5cc','#f7c09a',
'#7bafd4','#5bbcb0','#e88a56','#c5e0f8','#a8e0d8','#f9ceaa'];
function getDigit(n, pos) { return Math.floor(n / Math.pow(10, pos)) % 10; }
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// Generate a list of 12 distinct 3-digit numbers
function makeList() {
const base = [123,245,318,472,519,634,721,856,963,187,342,608];
return shuffle([...base]);
}
let arr = makeList();
let pass = 0; // 0=units, 1=tens, 2=hundreds; 3=done
function countingSortByDigit(a, pos) {
const count = new Array(10).fill(0);
a.forEach(v => count[getDigit(v, pos)]++);
// prefix sums (cumulative)
for (let i = 1; i < 10; i++) count[i] += count[i - 1];
const output = new Array(a.length);
// stable: iterate right to left
for (let i = a.length - 1; i >= 0; i--) {
const d = getDigit(a[i], pos);
output[--count[d]] = a[i];
}
return output;
}
// Verify sort correctness (used in node test)
function isSorted(a) {
for (let i = 1; i < a.length; i++) if (a[i] < a[i - 1]) return false;
return true;
}
const maxVal = 999;
function render(highlight) {
const barsEl = document.getElementById('bars');
const digitsEl = document.getElementById('digits-row');
barsEl.innerHTML = '';
digitsEl.innerHTML = '';
arr.forEach((v, i) => {
const wrap = document.createElement('div');
wrap.className = 'bar-wrap';
const bar = document.createElement('div');
bar.className = 'bar' + (highlight && highlight.has(i) ? ' highlighted' : '');
bar.style.height = Math.round((v / maxVal) * 160 + 12) + 'px';
bar.style.background = highlight && highlight.has(i) ? '' : PALETTE[i % PALETTE.length];
const label = document.createElement('div');
label.className = 'bar-val';
label.textContent = v;
wrap.appendChild(bar);
wrap.appendChild(label);
barsEl.appendChild(wrap);
// digit badge showing the active digit
const badge = document.createElement('div');
badge.className = 'digit-badge';
if (pass < 3) {
badge.style.background = DIGIT_COLORS[pass];
badge.textContent = getDigit(v, pass);
} else {
badge.style.background = '#0a7d33';
badge.textContent = '✓';
}
digitsEl.appendChild(badge);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + (cls || '');
}
function updatePassLabel() {
document.getElementById('pass-name').textContent =
pass < 3 ? DIGIT_NAMES[pass] : 'Done';
}
function step() {
if (pass >= 3) return;
arr = countingSortByDigit(arr, pass);
pass++;
render();
updatePassLabel();
if (pass === 3) {
setStatus('All 3 passes complete — array is sorted!', 'ok');
document.getElementById('step').disabled = true;
console.log('Sorted:', arr.join(', '), '| isSorted:', isSorted(arr));
} else {
setStatus(fmt('Pass %pass% done. Next: sort by %v%.', { pass: pass, v: DIGIT_NAMES[pass] }));
}
}
function reset() {
arr = makeList();
pass = 0;
document.getElementById('step').disabled = false;
setStatus('Press Step to begin.');
updatePassLabel();
render();
console.log('Reset. Initial:', arr.join(', '));
}
document.getElementById('step').addEventListener('click', step);
document.getElementById('reset').addEventListener('click', reset);
render();
updatePassLabel();
Comments
Loading comments...