The list below has 64 entries. A full scan would need 64 comparisons to verify sortedness. The tester below uses only O(logn) random probes.
<!-- Property-testing demo — sortedness via random index-pair probes -->
<p class="hint">The list has 64 values shown as bars. Each <b>Probe</b> picks two random indices i < j and checks a[i] ≤ a[j]. All probes passing → likely sorted; any failure → definitely unsorted. Only O(log n) probes are needed.</p>
<div class="controls">
<button id="btn-sorted" type="button">Make sorted</button>
<button id="btn-shuffle" type="button">Shuffle</button>
<button id="btn-probe" type="button" class="primary">Probe</button>
<button id="btn-reset" type="button" class="ghost">Reset probes</button>
</div>
<div id="array-vis" class="array-vis" aria-label="Array visualized as bars"></div>
<div id="status" class="status"></div>
<div id="probe-log" class="probe-log" aria-live="polite"></div>
/* Styles for the property-testing sortedness demo */
* { 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; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; }
button.primary { background: #e63946; border-color: #c92f3c; }
button.ghost { background: #fff; color: #1d3557; }
/* Bar chart area — bars colored by probe role */
.array-vis { display: flex; align-items: flex-end; gap: 2px; height: 80px;
background: #f0f4f8; border-radius: 8px; padding: 6px 6px 0;
overflow: hidden; }
.bar { flex: 1; border-radius: 3px 3px 0 0; background: #6b92b0;
transition: background .2s, height .15s; min-height: 2px; }
.bar.probed-lo { background: #2a9d8f; }
.bar.probed-hi { background: #e9c46a; }
.bar.violation { background: #e63946; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
/* Scrollable log of individual probes */
.probe-log { font-size: .82rem; color: #555; max-height: 120px;
overflow-y: auto; line-height: 1.5; }
.probe-log .entry { padding: .08rem 0; }
.probe-log .entry.fail { color: #c92f3c; font-weight: 600; }
.probe-log .entry.pass { color: #0a7d33; }
// Property-testing sortedness demo — O(log n) random pair probes
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const N = 64; // number of elements in the list
const MAX_PROBES = Math.ceil(Math.log2(N)) * 2; // run 2*ceil(log2(N)) probes before declaring likely-sorted
// Start with a perfectly sorted list [1..N]
let arr = Array.from({ length: N }, (_, i) => i + 1);
let probeCount = 0;
let done = false;
// Fisher-Yates in-place shuffle
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
}
// Draw bars; optionally highlight the probed pair or mark a violation
function render(highlightLo, highlightHi, violationPair) {
const vis = document.getElementById('array-vis');
vis.innerHTML = '';
const max = Math.max(...arr);
arr.forEach((v, i) => {
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.height = (v / max * 100) + '%';
if (violationPair && (i === violationPair[0] || i === violationPair[1])) {
bar.classList.add('violation');
} else if (highlightLo !== undefined && i === highlightLo) {
bar.classList.add('probed-lo');
} else if (highlightHi !== undefined && i === highlightHi) {
bar.classList.add('probed-hi');
}
vis.appendChild(bar);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
function addLogEntry(text, cls) {
const log = document.getElementById('probe-log');
const entry = document.createElement('div');
entry.className = 'entry ' + cls;
entry.textContent = text;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
// Pick a random pair (lo, hi) with lo < hi and compare arr[lo] vs arr[hi]
// a truly sorted list always passes; a far-from-sorted list fails with high probability
function doProbe() {
if (done) return;
const i = Math.floor(Math.random() * N);
const j = Math.floor(Math.random() * N);
const lo = Math.min(i, j);
const hi = Math.max(i, j);
if (lo === hi) { doProbe(); return; } // avoid trivial self-comparison
probeCount++;
const pass = arr[lo] <= arr[hi];
if (pass) {
render(lo, hi, null);
addLogEntry(
i18nFmt('Probe %p%: a[%lo%]=%vlo% ≤ a[%hi%]=%vhi% ✓', { lo: lo, hi: hi, vlo: arr[lo], vhi: arr[hi], p: probeCount }),
'pass'
);
if (probeCount >= MAX_PROBES) {
done = true;
setStatus(
i18nFmt('All %p% probes passed — likely sorted!', { p: probeCount }),
'ok'
);
} else {
setStatus(
i18nFmt('Probed %p% / %max% — no violation yet.', { p: probeCount, max: MAX_PROBES }),
'info'
);
}
} else {
render(lo, hi, [lo, hi]);
addLogEntry(
i18nFmt('Probe %p%: a[%lo%]=%vlo% > a[%hi%]=%vhi% ✗ VIOLATION', { lo: lo, hi: hi, vlo: arr[lo], vhi: arr[hi], p: probeCount }),
'fail'
);
done = true;
setStatus(
i18nFmt('Violation at indices %lo% and %hi% — NOT sorted!', { lo: lo, hi: hi }),
'bad'
);
}
}
function resetProbes() {
probeCount = 0;
done = false;
document.getElementById('probe-log').innerHTML = '';
setStatus('Choose sorted or shuffled, then click Probe.', 'info');
render();
}
document.getElementById('btn-sorted').addEventListener('click', () => {
arr = Array.from({ length: N }, (_, i) => i + 1);
resetProbes();
});
document.getElementById('btn-shuffle').addEventListener('click', () => {
arr = Array.from({ length: N }, (_, i) => i + 1);
shuffle(arr);
resetProbes();
});
document.getElementById('btn-probe').addEventListener('click', doProbe);
document.getElementById('btn-reset').addEventListener('click', resetProbes);
// Initial render on page load
render();
setStatus('Choose sorted or shuffled, then click Probe.', 'info');
<!-- Property-testing demo — sortedness via random index-pair probes -->
<p class="hint">The list has 64 values shown as bars. Each <b>Probe</b> picks two random indices i < j and checks a[i] ≤ a[j]. All probes passing → likely sorted; any failure → definitely unsorted. Only O(log n) probes are needed.</p>
<div class="controls">
<button id="btn-sorted" type="button">Make sorted</button>
<button id="btn-shuffle" type="button">Shuffle</button>
<button id="btn-probe" type="button" class="primary">Probe</button>
<button id="btn-reset" type="button" class="ghost">Reset probes</button>
</div>
<div id="array-vis" class="array-vis" aria-label="Array visualized as bars"></div>
<div id="status" class="status"></div>
<div id="probe-log" class="probe-log" aria-live="polite"></div>
/* Styles for the property-testing sortedness demo */
* { 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; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; }
button.primary { background: #e63946; border-color: #c92f3c; }
button.ghost { background: #fff; color: #1d3557; }
/* Bar chart area — bars colored by probe role */
.array-vis { display: flex; align-items: flex-end; gap: 2px; height: 80px;
background: #f0f4f8; border-radius: 8px; padding: 6px 6px 0;
overflow: hidden; }
.bar { flex: 1; border-radius: 3px 3px 0 0; background: #6b92b0;
transition: background .2s, height .15s; min-height: 2px; }
.bar.probed-lo { background: #2a9d8f; }
.bar.probed-hi { background: #e9c46a; }
.bar.violation { background: #e63946; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
/* Scrollable log of individual probes */
.probe-log { font-size: .82rem; color: #555; max-height: 120px;
overflow-y: auto; line-height: 1.5; }
.probe-log .entry { padding: .08rem 0; }
.probe-log .entry.fail { color: #c92f3c; font-weight: 600; }
.probe-log .entry.pass { color: #0a7d33; }
// Property-testing sortedness demo — O(log n) random pair probes
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const N = 64; // number of elements in the list
const MAX_PROBES = Math.ceil(Math.log2(N)) * 2; // run 2*ceil(log2(N)) probes before declaring likely-sorted
// Start with a perfectly sorted list [1..N]
let arr = Array.from({ length: N }, (_, i) => i + 1);
let probeCount = 0;
let done = false;
// Fisher-Yates in-place shuffle
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
}
// Draw bars; optionally highlight the probed pair or mark a violation
function render(highlightLo, highlightHi, violationPair) {
const vis = document.getElementById('array-vis');
vis.innerHTML = '';
const max = Math.max(...arr);
arr.forEach((v, i) => {
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.height = (v / max * 100) + '%';
if (violationPair && (i === violationPair[0] || i === violationPair[1])) {
bar.classList.add('violation');
} else if (highlightLo !== undefined && i === highlightLo) {
bar.classList.add('probed-lo');
} else if (highlightHi !== undefined && i === highlightHi) {
bar.classList.add('probed-hi');
}
vis.appendChild(bar);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
function addLogEntry(text, cls) {
const log = document.getElementById('probe-log');
const entry = document.createElement('div');
entry.className = 'entry ' + cls;
entry.textContent = text;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
// Pick a random pair (lo, hi) with lo < hi and compare arr[lo] vs arr[hi]
// a truly sorted list always passes; a far-from-sorted list fails with high probability
function doProbe() {
if (done) return;
const i = Math.floor(Math.random() * N);
const j = Math.floor(Math.random() * N);
const lo = Math.min(i, j);
const hi = Math.max(i, j);
if (lo === hi) { doProbe(); return; } // avoid trivial self-comparison
probeCount++;
const pass = arr[lo] <= arr[hi];
if (pass) {
render(lo, hi, null);
addLogEntry(
i18nFmt('Probe %p%: a[%lo%]=%vlo% ≤ a[%hi%]=%vhi% ✓', { lo: lo, hi: hi, vlo: arr[lo], vhi: arr[hi], p: probeCount }),
'pass'
);
if (probeCount >= MAX_PROBES) {
done = true;
setStatus(
i18nFmt('All %p% probes passed — likely sorted!', { p: probeCount }),
'ok'
);
} else {
setStatus(
i18nFmt('Probed %p% / %max% — no violation yet.', { p: probeCount, max: MAX_PROBES }),
'info'
);
}
} else {
render(lo, hi, [lo, hi]);
addLogEntry(
i18nFmt('Probe %p%: a[%lo%]=%vlo% > a[%hi%]=%vhi% ✗ VIOLATION', { lo: lo, hi: hi, vlo: arr[lo], vhi: arr[hi], p: probeCount }),
'fail'
);
done = true;
setStatus(
i18nFmt('Violation at indices %lo% and %hi% — NOT sorted!', { lo: lo, hi: hi }),
'bad'
);
}
}
function resetProbes() {
probeCount = 0;
done = false;
document.getElementById('probe-log').innerHTML = '';
setStatus('Choose sorted or shuffled, then click Probe.', 'info');
render();
}
document.getElementById('btn-sorted').addEventListener('click', () => {
arr = Array.from({ length: N }, (_, i) => i + 1);
resetProbes();
});
document.getElementById('btn-shuffle').addEventListener('click', () => {
arr = Array.from({ length: N }, (_, i) => i + 1);
shuffle(arr);
resetProbes();
});
document.getElementById('btn-probe').addEventListener('click', doProbe);
document.getElementById('btn-reset').addEventListener('click', resetProbes);
// Initial render on page load
render();
setStatus('Choose sorted or shuffled, then click Probe.', 'info');
Comments
Loading comments...