Sorting Networks
in progressThis lesson is still being built - content, demos, and checkpoints may change before release.
in progressThis lesson is still being built - content, demos, and checkpoints may change before release.
sorting-network.ts
import type { Tracer } from "../trace";import type { SortAlgorithm } from "./sort-algorithm";/** * Batcher's odd-even merge sort as a sorting network: a fixed schedule * of compare-exchange gates between wire positions. Runs of size p are * merged into runs of size 2p; within a merge, the gate distance k * halves from p down to 1. Every (p, k) pair is one layer of gates on * disjoint wires, so a whole layer could fire in parallel, and the * schedule depends only on n, never on the data. n must be a power of * two between minSize and maxSize. * * Time: O(n log² n) comparators, O(log² n) depth in parallel. Space: O(1) extra. */export class SortingNetwork implements SortAlgorithm { readonly name = "sorting-network"; readonly minSize = 4; readonly maxSize = 32; constructor(private readonly tracer?: Tracer) {} sort(input: readonly number[]): number[] { const a = [...input]; const array = [...input]; const n = a.length; const n = array.length; if (!this.supports(n)) { throw new RangeError(`n must be a power of two from 4 to 32, got ${n}`); } for (let p = 1; p < n; p *= 2) { for (let runSize = 1; runSize < n; runSize *= 2) { for (let k = p; k >= 1; k = Math.floor(k / 2)) { for (let stride = runSize; stride >= 1; stride = Math.floor(stride / 2)) { // ▸ the next layer of gates, all on disjoint wires this.tracer?.emit("layer", { k, p, array: [...a] }); this.tracer?.emit("layer", { stride, runSize, array: [...array] }); for (let j = k % p; j + k < n; j += 2 * k) { for (let j = stride % runSize; j + stride < n; j += 2 * stride) { for (let i = 0; i < k; i++) { for (let i = 0; i < stride; i++) { const lo = i + j; const hi = i + j + k; const hi = i + j + stride; // Gates never cross a 2p block: those wires merge later. if (Math.floor(lo / (2 * p)) !== Math.floor(hi / (2 * p))) continue; if (Math.floor(lo / (2 * runSize)) !== Math.floor(hi / (2 * runSize))) continue; // ▸ a gate looks at its two wires this.tracer?.emit("compare", { i: lo, j: hi, k, p, array: [...a] }); this.tracer?.emit("compare", { i: lo, j: hi, stride, runSize, array: [...array] }); if (a[lo]! > a[hi]!) { if (array[lo]! > array[hi]!) { // ▸ the lower wire holds the bigger value, exchange them this.tracer?.emit("swap", { i: lo, j: hi, k, p, array: [...a] }); this.tracer?.emit("swap", { i: lo, j: hi, stride, runSize, array: [...array] }); const held = a[lo]!; const held = array[lo]!; a[lo] = a[hi]!; array[lo] = array[hi]!; a[hi] = held; array[hi] = held; } } } } } return a; return array; } /** The network exists only for powers of two within the supported span. */ private supports(n: number): boolean { return n >= this.minSize && n <= this.maxSize && (n & (n - 1)) === 0; }}The array sits on 16 wires. The gates that follow are fixed by that count alone: play to watch them fire in their set order.