<p class="hint">A dynamic set of integers from <b>[0, 255]</b> (U=256, log U=8, log log U=3).
Insert or delete values, then find the <b>predecessor</b> of any key. The x-fast trie
layer holds one representative per cluster of up to 8 elements.</p>
<div class="controls">
<div class="row">
<input id="valInput" type="number" min="0" max="255" placeholder="0–255" />
<button id="btnInsert" type="button">Insert</button>
<button id="btnDelete" type="button">Delete</button>
</div>
<div class="row">
<input id="predInput" type="number" min="0" max="255" placeholder="Query key" />
<button id="btnPred" type="button">Predecessor</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
</div>
<div id="status" class="status"></div>
<div id="trace" class="trace"></div>
<div id="visual" class="visual"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .7rem; }
.row { display: flex; gap: .45rem; flex-wrap: wrap; align-items: center; }
input[type=number] { width: 90px; padding: .38rem .5rem; border: 1px solid #bbb;
border-radius: 6px; font-size: 14px; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 6px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-weight: 600; font-size: .95rem; min-height: 1.4em; margin: .3rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.trace { font-size: .82rem; color: #555; background: #f4f6f8; border-radius: 6px;
padding: .5rem .7rem; min-height: 2.4em; margin-bottom: .6rem;
font-family: ui-monospace, monospace; line-height: 1.6; white-space: pre-wrap; }
.visual { display: flex; flex-direction: column; gap: .4rem; }
.cluster-row { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.cluster-label { font-size: .75rem; color: #777; width: 54px; text-align: right;
font-family: ui-monospace, monospace; flex-shrink: 0; }
.chip { display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 28px; border-radius: 5px; font: 600 12px ui-monospace, monospace;
border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.chip.rep { border-color: #457b9d; background: #d0e4f0; }
.chip.hi { background: #e63946; border-color: #c92f3c; color: #fff; }
.chip.pred-chip { background: #2a9d8f; border-color: #1d7a6f; color: #fff; }
.empty-row { font-size: .8rem; color: #aaa; font-style: italic; margin-left: 60px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Y-fast trie simulation: U=256, log U=8, cluster size = log U = 8.
// We maintain clusters (balanced BSTs simulated as sorted arrays).
// The x-fast trie layer is represented by a Map: representative -> cluster index.
// This demo focuses on the algorithmic behavior, not a pixel-perfect production impl.
const U = 256;
const LOG_U = 8; // log2(256)
// Each cluster holds at most 2*LOG_U elements; target size LOG_U.
// Representative of a cluster = its maximum element.
// State: array of clusters, each cluster is a sorted array of integers.
let clusters = [];
function allElements() {
return clusters.flatMap(c => c).sort((a,b)=>a-b);
}
function findCluster(val) {
// Find the cluster whose representative (max) >= val (first such cluster).
for (let i = 0; i < clusters.length; i++) {
if (clusters[i][clusters[i].length - 1] >= val) return i;
}
return clusters.length; // beyond all clusters
}
function representative(cluster) {
return cluster[cluster.length - 1];
}
function insert(val) {
if (val < 0 || val >= U || !Number.isInteger(val))
return { ok: false, msg: 'Value must be an integer in [0, 255].' };
if (allElements().includes(val))
return { ok: false, msg: fmt('%val% is already in the set.', { val: val }) };
const trace = [];
trace.push(fmt('Insert %val%', { val: val }));
if (clusters.length === 0) {
clusters.push([val]);
trace.push(" " + fmt('Set was empty → created first cluster [%val%]', { val: val }));
return { ok: true, msg: fmt('Inserted %val%.', { val: val }), trace };
}
let ci = findCluster(val);
if (ci === clusters.length) {
// val is larger than all representatives → goes into last cluster or new one
ci = clusters.length - 1;
trace.push(" " + fmt('val > all representatives → target last cluster (rep=%representative%)', { representative: representative(clusters[ci]) }));
} else {
trace.push(" " + fmt('x-fast lookup: first cluster with rep ≥ %val% is cluster %ci% (rep=%representative%)', { val: val, ci: ci, representative: representative(clusters[ci]) }));
}
// Insert into sorted cluster
const cl = clusters[ci];
let pos = cl.findIndex(x => x > val);
if (pos === -1) pos = cl.length;
cl.splice(pos, 0, val);
trace.push(" " + fmt('Inserted into cluster %ci%: [%cl%]', { ci: ci, cl: cl.join(', ') }));
// Split if oversized (> 2 * LOG_U)
if (cl.length > 2 * LOG_U) {
const mid = Math.floor(cl.length / 2);
const left = cl.slice(0, mid);
const right = cl.slice(mid);
clusters.splice(ci, 1, left, right);
trace.push(" " + fmt('Cluster too large (>%v%) → → split into [%left%] and [%right%]', { v: 2*LOG_U, left: left.join(','), right: right.join(',') }));
}
return { ok: true, msg: fmt('Inserted %val%.', { val: val }), trace };
}
function del(val) {
if (val < 0 || val >= U || !Number.isInteger(val))
return { ok: false, msg: 'Value must be an integer in [0, 255].' };
const trace = [];
trace.push(fmt('Delete %val%', { val: val }));
let ci = findCluster(val);
if (ci >= clusters.length || !clusters[ci].includes(val)) {
return { ok: false, msg: fmt('%val% is not in the set.', { val: val }) };
}
trace.push(" " + fmt('Found in cluster %ci% (rep=%representative%)', { ci: ci, representative: representative(clusters[ci]) }));
const cl = clusters[ci];
const idx = cl.indexOf(val);
cl.splice(idx, 1);
trace.push(" " + fmt('Removed. Cluster %ci% now: [%v%]', { ci: ci, v: cl.length ? cl.join(', ') : 'empty' }));
if (cl.length === 0) {
clusters.splice(ci, 1);
trace.push(' Cluster empty → removed.');
} else if (cl.length < Math.floor(LOG_U / 2) && clusters.length > 1) {
// Merge with neighbor
if (ci > 0) {
clusters[ci-1] = clusters[ci-1].concat(clusters[ci]).sort((a,b)=>a-b);
clusters.splice(ci, 1);
trace.push(" " + fmt('Cluster too small (<%floor%) → → merged with left neighbor.', { floor: Math.floor(LOG_U/2) }));
} else {
clusters[ci] = clusters[ci].concat(clusters[ci+1]).sort((a,b)=>a-b);
clusters.splice(ci+1, 1);
trace.push(" " + fmt('Cluster too small (<%floor%) → → merged with right neighbor.', { floor: Math.floor(LOG_U/2) }));
}
}
return { ok: true, msg: fmt('Deleted %val%.', { val: val }), trace };
}
function predecessor(key) {
if (key < 0 || key >= U || !Number.isInteger(key))
return { ok: false, msg: 'Key must be an integer in [0, 255].' };
const trace = [];
trace.push('Predecessor(' + key + ')');
if (clusters.length === 0)
return { ok: false, msg: 'Set is empty.', trace };
// Step 1: x-fast trie layer — find first cluster with rep >= key
let ci = findCluster(key);
trace.push(" " + fmt('Step 1 (x-fast layer): first cluster with rep ≥ %key%', { key: key }));
let predVal = null;
let predCluster = -1;
if (ci < clusters.length) {
const cl = clusters[ci];
trace.push(" " + fmt('→ cluster %ci% [rep=%representative%]', { ci: ci, representative: representative(cl) }));
// Step 2: local search within cluster
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = ci;
trace.push(" " + fmt('Step 2 (local BST search in cluster %ci%): max ≤ %key% is %predVal%', { ci: ci, key: key, predVal: predVal }));
} else {
trace.push(" " + fmt('Step 2: no element ≤ %key% in cluster %ci% → check previous cluster', { key: key, ci: ci }));
}
} else {
trace.push(' → key > all representatives, check last cluster');
}
if (predVal === null && (ci === clusters.length || clusters[ci].filter(x=>x<=key).length===0)) {
// Check previous cluster
const prevCi = ci > 0 ? ci - 1 : -1;
if (prevCi >= 0) {
const cl = clusters[prevCi];
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = prevCi;
trace.push(" " + fmt('Step 2 (local BST search in cluster %prevCi%): max ≤ %key% is %predVal%', { prevCi: prevCi, key: key, predVal: predVal }));
}
} else if (ci >= clusters.length) {
// Last cluster
const cl = clusters[clusters.length - 1];
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = clusters.length - 1;
trace.push(" " + fmt('Step 2 (local BST search in last cluster): max ≤ %key% is %predVal%', { key: key, predVal: predVal }));
}
}
}
if (predVal === null) {
trace.push(" " + fmt('No predecessor found (all elements > %key%).', { key: key }));
return { ok: false, msg: fmt('No predecessor of %key% in the set.', { key: key }), trace };
}
trace.push(" " + fmt('Predecessor of %key% = %predVal% (O(log log U) = O(3) steps)', { key: key, predVal: predVal }));
return { ok: true, msg: fmt('Predecessor of %key% = %predVal%', { key: key, predVal: predVal }), trace, predVal, predCluster };
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg; el.className = 'status ' + (cls||'info');
}
function setTrace(lines) {
document.getElementById('trace').textContent = lines.join('\n');
}
let lastPred = null;
let lastPredCluster = -1;
function render() {
const vis = document.getElementById('visual');
vis.innerHTML = '';
if (clusters.length === 0) {
const e = document.createElement('div');
e.className = 'empty-row'; e.textContent = '(set is empty)';
vis.appendChild(e); return;
}
clusters.forEach((cl, ci) => {
const row = document.createElement('div');
row.className = 'cluster-row';
const lbl = document.createElement('div');
lbl.className = 'cluster-label';
lbl.textContent = 'C' + ci + ':';
row.appendChild(lbl);
cl.forEach((v, vi) => {
const chip = document.createElement('div');
const isRep = (vi === cl.length - 1);
let cls = 'chip';
if (ci === lastPredCluster && v === lastPred) cls += ' pred-chip';
else if (isRep) cls += ' rep';
chip.className = cls;
chip.textContent = v;
chip.title = isRep ? 'representative' : '';
row.appendChild(chip);
});
vis.appendChild(row);
});
}
function parseInput(id) {
return parseInt(document.getElementById(id).value, 10);
}
document.getElementById('btnInsert').addEventListener('click', () => {
const v = parseInput('valInput');
const r = insert(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = null; lastPredCluster = -1;
render();
});
document.getElementById('btnDelete').addEventListener('click', () => {
const v = parseInput('valInput');
const r = del(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = null; lastPredCluster = -1;
render();
});
document.getElementById('btnPred').addEventListener('click', () => {
const v = parseInput('predInput');
const r = predecessor(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = r.ok ? r.predVal : null;
lastPredCluster = r.ok ? r.predCluster : -1;
render();
});
document.getElementById('btnReset').addEventListener('click', () => {
clusters = [];
lastPred = null; lastPredCluster = -1;
setStatus('', '');
document.getElementById('trace').textContent = '';
render();
});
// Seed with some initial values
[12, 25, 37, 58, 71, 89, 103, 120, 145, 160, 178, 200, 230, 247].forEach(v => insert(v));
setStatus('Set seeded with 14 values. Try predecessor(100) or insert your own.', 'info');
setTrace(['Ready. Insert values and query predecessor.']);
render();
<p class="hint">A dynamic set of integers from <b>[0, 255]</b> (U=256, log U=8, log log U=3).
Insert or delete values, then find the <b>predecessor</b> of any key. The x-fast trie
layer holds one representative per cluster of up to 8 elements.</p>
<div class="controls">
<div class="row">
<input id="valInput" type="number" min="0" max="255" placeholder="0–255" />
<button id="btnInsert" type="button">Insert</button>
<button id="btnDelete" type="button">Delete</button>
</div>
<div class="row">
<input id="predInput" type="number" min="0" max="255" placeholder="Query key" />
<button id="btnPred" type="button">Predecessor</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
</div>
<div id="status" class="status"></div>
<div id="trace" class="trace"></div>
<div id="visual" class="visual"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .7rem; }
.row { display: flex; gap: .45rem; flex-wrap: wrap; align-items: center; }
input[type=number] { width: 90px; padding: .38rem .5rem; border: 1px solid #bbb;
border-radius: 6px; font-size: 14px; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 6px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-weight: 600; font-size: .95rem; min-height: 1.4em; margin: .3rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.trace { font-size: .82rem; color: #555; background: #f4f6f8; border-radius: 6px;
padding: .5rem .7rem; min-height: 2.4em; margin-bottom: .6rem;
font-family: ui-monospace, monospace; line-height: 1.6; white-space: pre-wrap; }
.visual { display: flex; flex-direction: column; gap: .4rem; }
.cluster-row { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.cluster-label { font-size: .75rem; color: #777; width: 54px; text-align: right;
font-family: ui-monospace, monospace; flex-shrink: 0; }
.chip { display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 28px; border-radius: 5px; font: 600 12px ui-monospace, monospace;
border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.chip.rep { border-color: #457b9d; background: #d0e4f0; }
.chip.hi { background: #e63946; border-color: #c92f3c; color: #fff; }
.chip.pred-chip { background: #2a9d8f; border-color: #1d7a6f; color: #fff; }
.empty-row { font-size: .8rem; color: #aaa; font-style: italic; margin-left: 60px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Y-fast trie simulation: U=256, log U=8, cluster size = log U = 8.
// We maintain clusters (balanced BSTs simulated as sorted arrays).
// The x-fast trie layer is represented by a Map: representative -> cluster index.
// This demo focuses on the algorithmic behavior, not a pixel-perfect production impl.
const U = 256;
const LOG_U = 8; // log2(256)
// Each cluster holds at most 2*LOG_U elements; target size LOG_U.
// Representative of a cluster = its maximum element.
// State: array of clusters, each cluster is a sorted array of integers.
let clusters = [];
function allElements() {
return clusters.flatMap(c => c).sort((a,b)=>a-b);
}
function findCluster(val) {
// Find the cluster whose representative (max) >= val (first such cluster).
for (let i = 0; i < clusters.length; i++) {
if (clusters[i][clusters[i].length - 1] >= val) return i;
}
return clusters.length; // beyond all clusters
}
function representative(cluster) {
return cluster[cluster.length - 1];
}
function insert(val) {
if (val < 0 || val >= U || !Number.isInteger(val))
return { ok: false, msg: 'Value must be an integer in [0, 255].' };
if (allElements().includes(val))
return { ok: false, msg: fmt('%val% is already in the set.', { val: val }) };
const trace = [];
trace.push(fmt('Insert %val%', { val: val }));
if (clusters.length === 0) {
clusters.push([val]);
trace.push(" " + fmt('Set was empty → created first cluster [%val%]', { val: val }));
return { ok: true, msg: fmt('Inserted %val%.', { val: val }), trace };
}
let ci = findCluster(val);
if (ci === clusters.length) {
// val is larger than all representatives → goes into last cluster or new one
ci = clusters.length - 1;
trace.push(" " + fmt('val > all representatives → target last cluster (rep=%representative%)', { representative: representative(clusters[ci]) }));
} else {
trace.push(" " + fmt('x-fast lookup: first cluster with rep ≥ %val% is cluster %ci% (rep=%representative%)', { val: val, ci: ci, representative: representative(clusters[ci]) }));
}
// Insert into sorted cluster
const cl = clusters[ci];
let pos = cl.findIndex(x => x > val);
if (pos === -1) pos = cl.length;
cl.splice(pos, 0, val);
trace.push(" " + fmt('Inserted into cluster %ci%: [%cl%]', { ci: ci, cl: cl.join(', ') }));
// Split if oversized (> 2 * LOG_U)
if (cl.length > 2 * LOG_U) {
const mid = Math.floor(cl.length / 2);
const left = cl.slice(0, mid);
const right = cl.slice(mid);
clusters.splice(ci, 1, left, right);
trace.push(" " + fmt('Cluster too large (>%v%) → → split into [%left%] and [%right%]', { v: 2*LOG_U, left: left.join(','), right: right.join(',') }));
}
return { ok: true, msg: fmt('Inserted %val%.', { val: val }), trace };
}
function del(val) {
if (val < 0 || val >= U || !Number.isInteger(val))
return { ok: false, msg: 'Value must be an integer in [0, 255].' };
const trace = [];
trace.push(fmt('Delete %val%', { val: val }));
let ci = findCluster(val);
if (ci >= clusters.length || !clusters[ci].includes(val)) {
return { ok: false, msg: fmt('%val% is not in the set.', { val: val }) };
}
trace.push(" " + fmt('Found in cluster %ci% (rep=%representative%)', { ci: ci, representative: representative(clusters[ci]) }));
const cl = clusters[ci];
const idx = cl.indexOf(val);
cl.splice(idx, 1);
trace.push(" " + fmt('Removed. Cluster %ci% now: [%v%]', { ci: ci, v: cl.length ? cl.join(', ') : 'empty' }));
if (cl.length === 0) {
clusters.splice(ci, 1);
trace.push(' Cluster empty → removed.');
} else if (cl.length < Math.floor(LOG_U / 2) && clusters.length > 1) {
// Merge with neighbor
if (ci > 0) {
clusters[ci-1] = clusters[ci-1].concat(clusters[ci]).sort((a,b)=>a-b);
clusters.splice(ci, 1);
trace.push(" " + fmt('Cluster too small (<%floor%) → → merged with left neighbor.', { floor: Math.floor(LOG_U/2) }));
} else {
clusters[ci] = clusters[ci].concat(clusters[ci+1]).sort((a,b)=>a-b);
clusters.splice(ci+1, 1);
trace.push(" " + fmt('Cluster too small (<%floor%) → → merged with right neighbor.', { floor: Math.floor(LOG_U/2) }));
}
}
return { ok: true, msg: fmt('Deleted %val%.', { val: val }), trace };
}
function predecessor(key) {
if (key < 0 || key >= U || !Number.isInteger(key))
return { ok: false, msg: 'Key must be an integer in [0, 255].' };
const trace = [];
trace.push('Predecessor(' + key + ')');
if (clusters.length === 0)
return { ok: false, msg: 'Set is empty.', trace };
// Step 1: x-fast trie layer — find first cluster with rep >= key
let ci = findCluster(key);
trace.push(" " + fmt('Step 1 (x-fast layer): first cluster with rep ≥ %key%', { key: key }));
let predVal = null;
let predCluster = -1;
if (ci < clusters.length) {
const cl = clusters[ci];
trace.push(" " + fmt('→ cluster %ci% [rep=%representative%]', { ci: ci, representative: representative(cl) }));
// Step 2: local search within cluster
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = ci;
trace.push(" " + fmt('Step 2 (local BST search in cluster %ci%): max ≤ %key% is %predVal%', { ci: ci, key: key, predVal: predVal }));
} else {
trace.push(" " + fmt('Step 2: no element ≤ %key% in cluster %ci% → check previous cluster', { key: key, ci: ci }));
}
} else {
trace.push(' → key > all representatives, check last cluster');
}
if (predVal === null && (ci === clusters.length || clusters[ci].filter(x=>x<=key).length===0)) {
// Check previous cluster
const prevCi = ci > 0 ? ci - 1 : -1;
if (prevCi >= 0) {
const cl = clusters[prevCi];
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = prevCi;
trace.push(" " + fmt('Step 2 (local BST search in cluster %prevCi%): max ≤ %key% is %predVal%', { prevCi: prevCi, key: key, predVal: predVal }));
}
} else if (ci >= clusters.length) {
// Last cluster
const cl = clusters[clusters.length - 1];
const local = cl.filter(x => x <= key);
if (local.length > 0) {
predVal = local[local.length - 1];
predCluster = clusters.length - 1;
trace.push(" " + fmt('Step 2 (local BST search in last cluster): max ≤ %key% is %predVal%', { key: key, predVal: predVal }));
}
}
}
if (predVal === null) {
trace.push(" " + fmt('No predecessor found (all elements > %key%).', { key: key }));
return { ok: false, msg: fmt('No predecessor of %key% in the set.', { key: key }), trace };
}
trace.push(" " + fmt('Predecessor of %key% = %predVal% (O(log log U) = O(3) steps)', { key: key, predVal: predVal }));
return { ok: true, msg: fmt('Predecessor of %key% = %predVal%', { key: key, predVal: predVal }), trace, predVal, predCluster };
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg; el.className = 'status ' + (cls||'info');
}
function setTrace(lines) {
document.getElementById('trace').textContent = lines.join('\n');
}
let lastPred = null;
let lastPredCluster = -1;
function render() {
const vis = document.getElementById('visual');
vis.innerHTML = '';
if (clusters.length === 0) {
const e = document.createElement('div');
e.className = 'empty-row'; e.textContent = '(set is empty)';
vis.appendChild(e); return;
}
clusters.forEach((cl, ci) => {
const row = document.createElement('div');
row.className = 'cluster-row';
const lbl = document.createElement('div');
lbl.className = 'cluster-label';
lbl.textContent = 'C' + ci + ':';
row.appendChild(lbl);
cl.forEach((v, vi) => {
const chip = document.createElement('div');
const isRep = (vi === cl.length - 1);
let cls = 'chip';
if (ci === lastPredCluster && v === lastPred) cls += ' pred-chip';
else if (isRep) cls += ' rep';
chip.className = cls;
chip.textContent = v;
chip.title = isRep ? 'representative' : '';
row.appendChild(chip);
});
vis.appendChild(row);
});
}
function parseInput(id) {
return parseInt(document.getElementById(id).value, 10);
}
document.getElementById('btnInsert').addEventListener('click', () => {
const v = parseInput('valInput');
const r = insert(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = null; lastPredCluster = -1;
render();
});
document.getElementById('btnDelete').addEventListener('click', () => {
const v = parseInput('valInput');
const r = del(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = null; lastPredCluster = -1;
render();
});
document.getElementById('btnPred').addEventListener('click', () => {
const v = parseInput('predInput');
const r = predecessor(v);
setStatus(r.msg, r.ok ? 'ok' : 'bad');
if (r.trace) setTrace(r.trace);
lastPred = r.ok ? r.predVal : null;
lastPredCluster = r.ok ? r.predCluster : -1;
render();
});
document.getElementById('btnReset').addEventListener('click', () => {
clusters = [];
lastPred = null; lastPredCluster = -1;
setStatus('', '');
document.getElementById('trace').textContent = '';
render();
});
// Seed with some initial values
[12, 25, 37, 58, 71, 89, 103, 120, 145, 160, 178, 200, 230, 247].forEach(v => insert(v));
setStatus('Set seeded with 14 values. Try predecessor(100) or insert your own.', 'info');
setTrace(['Ready. Insert values and query predecessor.']);
render();
Notice how the search never scans the whole sorted list. It first narrows down to the right cluster using a hash-table lookup on the representative keys — that is the x-fast trie layer. Then it does a tiny local search within a cluster that holds at most O(logU)=8 elements. Compare that with a binary search over all n elements: here the cluster size is bounded by a constant of the universe, not by n.
Comments
Loading comments...