All files / random / sample.ts

93.33% Branches 14/15
93.55% Lines 29/31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
 
 
 
x8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x8
x8
x8
 
x3090
 
x1030
x2037
x2038
x2038
 
x2038
 
x2037
 
x3042
 
x2037
x2038
x2038
 
x2038
 
x2037
x2037
 
x2037
x4325
x4325
x5329
x5329
x4325
 
 
 
 
 
x2066
x1030
x1030
x1030
x1030





















































































I









// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import type { Prng, RandomOptions } from "./_types.ts";
import { randomIntegerBetween } from "./integer_between.ts";
export type { Prng, RandomOptions };

/**
 * Options for {@linkcode sample}.
 *
 * @experimental **UNSTABLE**: New API, yet to be vetted.
 */
export type SampleOptions = RandomOptions & {
  /**
   * An array of weights corresponding to each item in the input array.
   * If supplied, this is used to determine the probability of each item being
   * selected.
   */
  weights?: ArrayLike<number>;
};

/**
 * Returns a random element from the given array.
 *
 * @experimental **UNSTABLE**: New API, yet to be vetted.
 *
 * @typeParam T The type of the elements in the array.
 * @typeParam O The type of the accumulator.
 *
 * @param array The array to sample from.
 * @param options Options modifying the sampling behavior.
 *
 * @returns A random element from the given array, or `undefined` if the array
 * is empty.
 *
 * @example Basic usage
 * ```ts
 * import { sample } from "@std/random/sample";
 * import { assertArrayIncludes } from "@std/assert";
 *
 * const numbers = [1, 2, 3, 4];
 * const sampled = sample(numbers);
 *
 * assertArrayIncludes(numbers, [sampled]);
 * ```
 *
 * @example Using `weights` option
 * ```ts no-assert
 * import { sample } from "@std/random/sample";
 *
 * const values = ["a", "b", "c"];
 * const weights = [5, 3, 2];
 * const result = sample(values, { weights });
 * // gives "a" 50% of the time, "b" 30% of the time, and "c" 20% of the time
 * ```
 */
export function sample<T>(
  array: ArrayLike<T>,
  options?: SampleOptions,
): T | undefined {
  const { weights } = { ...options };

  if (weights) {
    if (weights.length !== array.length) {
      throw new RangeError(
        "Cannot sample an item: The length of the weights array must match the length of the input array",
      );
    }

    if (!array.length) return undefined;

    const total = Object.values(weights).reduce((sum, n) => sum + n, 0);

    if (total <= 0) {
      throw new RangeError(
        "Cannot sample an item: Total weight must be greater than 0",
      );
    }

    const rand = (options?.prng ?? Math.random)() * total;
    let current = 0;

    for (let i = 0; i < array.length; ++i) {
      current += weights[i]!;
      if (rand < current) {
        return array[i]!;
      }
    }

    // this line should never be hit, but in case of rounding errors etc.
    return array[0]!;
  }

  const length = array.length;
  return length
    ? array[randomIntegerBetween(0, length - 1, options)]
    : undefined;
}