mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
- Share adaptive stable sorting across arrays and byte arrays, reusing captured values and merge inputs. - Use bounded-domain ordering for default byte sorting. - Preserve sparse values, stable ties, callback effects, and reference lifetimes with native and portable coverage.
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
type Entry = { key: number; order: number; label: string };
|
|
|
|
function check(entries: Entry[]): void {
|
|
const before = entries.map(entry => entry.order).join(",");
|
|
const sorted = entries.toSorted((a, b) => a.key - b.key);
|
|
for (let i = 1; i < sorted.length; i++) {
|
|
const previous = sorted[i - 1]!;
|
|
const current = sorted[i]!;
|
|
if (previous.key > current.key || (previous.key === current.key && previous.order > current.order)) {
|
|
throw new Error("unstable ordering");
|
|
}
|
|
}
|
|
if (before !== entries.map(entry => entry.order).join(",")) throw new Error("source changed");
|
|
const same = entries.sort((a, b) => a.key - b.key);
|
|
console.log(same === entries, sorted.map(entry => entry.label).join(",") === entries.map(entry => entry.label).join(","));
|
|
console.log(sorted.map(entry => entry.order).join(","));
|
|
if (sorted.length > 0) {
|
|
sorted[0]!.label = "shared";
|
|
console.log(entries[0]!.label);
|
|
}
|
|
}
|
|
|
|
for (const size of [0, 1, 2, 15, 16, 17, 31, 32, 33, 65, 127, 257]) {
|
|
const entries: Entry[] = [];
|
|
for (let i = 0; i < size; i++) entries.push({ key: (i * 97) % 23, order: i, label: String(i) });
|
|
check(entries);
|
|
}
|
|
|
|
// Descending runs containing ties cannot reverse the equal entries.
|
|
const blocks: Entry[] = [];
|
|
for (let i = 0; i < 513; i++) {
|
|
blocks.push({ key: 100 - Math.floor(i / 3) % 100, order: i, label: "entry-" + String(i) });
|
|
}
|
|
check(blocks);
|
|
|
|
// A throw during a later merge must retain the original array and every
|
|
// referenced record, including values previously held in the scratch array.
|
|
const throwing: Entry[] = [];
|
|
for (let i = 0; i < 257; i++) throwing.push({ key: (i * 73) % 257, order: i, label: String(i) });
|
|
let comparisons = 0;
|
|
try {
|
|
throwing.sort((a, b) => {
|
|
comparisons++;
|
|
if (comparisons === 700) throw new Error("stop");
|
|
return a.key - b.key;
|
|
});
|
|
} catch (error) {
|
|
console.log(error instanceof Error, throwing.length, throwing[0]!.label, throwing[256]!.label);
|
|
}
|
|
|
|
const words: string[] = [];
|
|
for (let i = 0; i < 97; i++) words.push(["a", "\u{10000}", "\ue000", "é", "aa"][i % 5]!);
|
|
console.log(words.toSorted().join("|"));
|
|
|
|
const sparse: (Entry | undefined)[] = [];
|
|
for (let i = 0; i < 97; i++) {
|
|
if (i % 3 !== 0) sparse[i] = { key: (i * 29) % 97, order: i, label: String(i) };
|
|
}
|
|
sparse[5] = undefined;
|
|
sparse[96] = undefined;
|
|
let changed = false;
|
|
const copied = sparse.toSorted((a, b) => {
|
|
if (a === undefined || b === undefined) throw new Error("undefined comparator argument");
|
|
if (!changed) {
|
|
changed = true;
|
|
sparse.length = 1;
|
|
sparse.push({ key: -1, order: 100, label: "new" });
|
|
}
|
|
return a.key - b.key;
|
|
});
|
|
let visited = 0;
|
|
copied.forEach(() => { visited++; });
|
|
console.log(copied.length, visited, copied[0]?.key, copied[96] === undefined);
|
|
console.log(sparse.length, sparse[1]?.label);
|