Files
skills/docs/foundation/random/pseudo-random/src/utils/shuffle.md
2026-05-29 08:20:56 +01:00

2.3 KiB

utils/shuffle

Purpose

The shuffle module provides deterministic Fisher-Yates shuffling and single-element choice drawn from a user-supplied Rng. Because Rng is the canonical interface defined in @suaveplan/types, these utilities compose with every SeededRng implementation in this package as well as with the CSPRNG in @suaveplan/random.

Shuffling is a building block for game-card dealing, playlist generation, A/B assignment, and deterministic test data. Isolating it here ensures every downstream package uses the same unbiased Fisher-Yates algorithm rather than re-implementing it (which frequently introduces subtle bias).

Features

  • shuffle(input, rng) — non-mutating, returns a new shuffled array
  • shuffleInPlace(arr, rng) — mutates the argument, returns it
  • choice(input, rng) — uniformly random element; typed PseudoRandomError on empty input
  • Draws exactly n - 1 uint32 values per shuffle — counts are predictable for replay

Usage: Basic Example

import { Mulberry32Rng } from "@suaveplan/pseudo-random";
import { shuffle } from "@suaveplan/pseudo-random/utils";

const rng = new Mulberry32Rng(42);
const deck = shuffle(["A", "K", "Q", "J"], rng);

Usage: Advanced Example

import { Sfc32Rng } from "@suaveplan/pseudo-random";
import { choice, shuffleInPlace } from "@suaveplan/pseudo-random/utils";

const rng = new Sfc32Rng("round-12");
const deck = ["clubs", "hearts", "diamonds", "spades"];
shuffleInPlace(deck, rng);
const first = choice(deck, rng);

API Reference

  • shuffle<T>(input: ReadonlyArray<T>, rng: Rng): T[] — returns a new array.
  • shuffleInPlace<T>(arr: T[], rng: Rng): T[] — mutates and returns.
  • choice<T>(input: ReadonlyArray<T>, rng: Rng): T — throws PseudoRandomError.ARG.EMPTY_ARRAY on empty input.

Implementation Notes

The Fisher-Yates loop walks from n-1 down to 1, swapping arr[i] with arr[j] where j = rng.nextUint32() % (i+1). The modulo introduces a negligible bias when i+1 does not divide 2^32 evenly (max bias ≈ n / 2^32); for any n short of 2^32 elements this is statistically undetectable and matches the behaviour every other mainstream library ships. If you need provably unbiased sampling for large n, use @suaveplan/random's randomInt, which re-draws on biased samples.