# utils/sample ## Purpose The `sample` module covers the two sampling primitives that sit above `shuffle`: unique-without-replacement `sample` for drawing `k` items from `n`, and `weightedChoice` for proportional index selection. Both accept any `Rng` and throw typed `PseudoRandomError`s on invalid input. These primitives are intentionally small and allocation-frugal: `sample` reuses `shuffle` then slices, so we never allocate auxiliary structures beyond the shuffled copy; `weightedChoice` walks the weights once to compute the total, then once more to find the tipping point. ## Features - `sample(input, count, rng)` — unique draw without replacement - `weightedChoice(weights, rng)` — proportional index draw; rejects negative, NaN or all-zero weights - Both deterministic for equal seeds + equal RNG class - Strict input validation (positive integer counts, finite non-negative weights) ## Usage: Basic Example ```ts import { Mulberry32Rng } from "@suaveplan/pseudo-random"; import { sample, weightedChoice } from "@suaveplan/pseudo-random/utils"; const rng = new Mulberry32Rng(1); const hand = sample(["A", "K", "Q", "J", "10"], 3, rng); const bucket = weightedChoice([1, 2, 4], rng); // 0, 1 or 2 ``` ## Usage: Advanced Example ```ts import { Sfc32Rng } from "@suaveplan/pseudo-random"; import { weightedChoice } from "@suaveplan/pseudo-random/utils"; type Drop = { id: string; weight: number }; const drops: Drop[] = [ { id: "common", weight: 70 }, { id: "rare", weight: 25 }, { id: "epic", weight: 5 }, ]; const rng = new Sfc32Rng("loot-table"); const idx = weightedChoice(drops.map((d) => d.weight), rng); const chosen = drops[idx]; ``` ## API Reference - `sample(input, count, rng)` — `PseudoRandomError.ARG.NEGATIVE_COUNT` or `ARG.INVALID` on bad count; `count === 0` returns `[]`. - `weightedChoice(weights, rng)` — `ARG.EMPTY_ARRAY` on empty input; `ARG.WEIGHTS_INVALID` on negative, NaN or all-zero weights. ## Implementation Notes `weightedChoice` draws `rng.nextFloat()` (a 32-bit uniform in `[0, 1)`), multiplies by the total weight, and walks the cumulative sum. The final fallback (`return weights.length - 1`) guards against floating-point rounding that would otherwise fail to find a bucket when the target sits exactly on the total.