1/**2 * Largest-remainder apportionment (binding integer-discipline rule): split3 * `total` across `weights` proportionally; quotas are floored and the4 * leftover units go to the largest remainders, ties broken by ascending5 * index. Zero/negative totals or all-zero weights yield all zeros.6 * The result always sums to exactly `total` when any weight is positive.7 */8export function apportion(total: number, weights: readonly number[]): number[] {9 const n = weights.length;10 const out = new Array<number>(n).fill(0);11 let sum = 0;12 for (let i = 0; i < n; i++) sum += weights[i]!;13 if (total <= 0 || sum <= 0) return out;14 let assigned = 0;15 const remainders = new Array<number>(n);16 for (let i = 0; i < n; i++) {17 const exact = total * weights[i]!;18 const q = Math.trunc(exact / sum);19 out[i] = q;20 assigned += q;21 remainders[i] = exact - q * sum;22 }23 let left = total - assigned;24 if (left > 0) {25 const order = Array.from({ length: n }, (_, i) => i);26 order.sort((a, b) => remainders[b]! - remainders[a]! || a - b);27 for (let k = 0; left > 0 && k < n; k++) {28 out[order[k]!]! += 1;29 left--;30 }31 }32 return out;33}