import { BitArray } from "./bitarray.js"; import { asArray } from "./utils.js"; export type ElementPredicate = (index: number, obj: T) => boolean; export type ElementWeight = (index: number, obj: T) => number; export type RandomGenerator = () => number; export interface RandomOptions { predicate?: ElementPredicate; weight?: ElementWeight; random?: RandomGenerator; }; export const alwaysTrue: ElementPredicate = () => true; export const weightOfOne: ElementWeight = () => 1.0; export const mathRandom: RandomGenerator = () => Math.random(); const defaultOptions = Object.freeze>({ predicate: alwaysTrue, weight: weightOfOne, random: mathRandom }); function mergeOptions(first: RandomOptions | undefined, second: RandomOptions | undefined): RandomOptions | undefined { if (!first) { return second; } if (!second) { return first; } const firstPredicate = first.predicate; const secondPredicate = second.predicate; return { predicate: firstPredicate ? secondPredicate ? (i, o) => firstPredicate(i, o) && secondPredicate(i, o) : firstPredicate : secondPredicate, weight: first.weight ?? second.weight, random: first.random ?? second.random }; } function withDefaultOptions(options: RandomOptions | undefined): Required> { if (!options) { return defaultOptions; } return { predicate: options.predicate ?? defaultOptions.predicate, weight: options.weight ?? defaultOptions.weight, random: options.random ?? defaultOptions.random }; } export function getRandomElement(sequence: Iterable, options?: RandomOptions) { const { predicate, weight, random } = withDefaultOptions(options); let result: T | undefined = undefined; let resultIndex = -1; let index = 0; let weightAcc = 0.0; for (const element of sequence) { const currentIndex = index++; if (predicate(currentIndex, element)) { const w = weight(currentIndex, element); if (w <= 0.0) { continue; } weightAcc += w; if (random() * weightAcc < w) { result = element; resultIndex = currentIndex; } } } return { element: result, index: resultIndex }; } export class RandomPicker { readonly #elements: Iterable; readonly #flags: BitArray; readonly #options: RandomOptions; public constructor(elements: T[], options?: RandomOptions) { this.#elements = elements; this.#flags = new BitArray(elements.length); this.#options = withDefaultOptions(mergeOptions({ predicate: i => this.#flags.get(i) }, options)); this.reset(); } public static from(iterable: Iterable, options?: RandomOptions) { return new this(asArray(iterable), options); } public static of(options?: RandomOptions, ...values: T[]) { return new this(values, options); } public get state() { return this.#flags; } public reset() { this.#flags.fill(true); } public next() { const result = getRandomElement(this.#elements, this.#options); if (result.index < 0) { this.reset(); } else { this.#flags.set(result.index, false); if (this.#flags.isEmpty()) { this.reset(); } } return result.element; } }