Files
suaveplan/packages/foundation/random/pseudo-random/src/utils/sample.md
2026-05-27 23:47:53 +01:00

2.2 KiB

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 PseudoRandomErrors 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

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

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<T>(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.