UNPKG

4.45 kBJavaScriptView Raw
1import { factory } from '../../utils/factory'
2import { isNumber } from '../../utils/is'
3import { arraySize } from '../../utils/array'
4import { createRng } from './util/seededRNG'
5
6const name = 'pickRandom'
7const dependencies = ['typed', 'config', '?on']
8
9export const createPickRandom = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, on }) => {
10 // seeded pseudo random number generator
11 let rng = createRng(config.randomSeed)
12
13 if (on) {
14 on('config', function (curr, prev) {
15 if (curr.randomSeed !== prev.randomSeed) {
16 rng = createRng(curr.randomSeed)
17 }
18 })
19 }
20
21 /**
22 * Random pick one or more values from a one dimensional array.
23 * Array elements are picked using a random function with uniform or weighted distribution.
24 *
25 * Syntax:
26 *
27 * math.pickRandom(array)
28 * math.pickRandom(array, number)
29 * math.pickRandom(array, weights)
30 * math.pickRandom(array, number, weights)
31 * math.pickRandom(array, weights, number)
32 *
33 * Examples:
34 *
35 * math.pickRandom([3, 6, 12, 2]) // returns one of the values in the array
36 * math.pickRandom([3, 6, 12, 2], 2) // returns an array of two of the values in the array
37 * math.pickRandom([3, 6, 12, 2], [1, 3, 2, 1]) // returns one of the values in the array with weighted distribution
38 * math.pickRandom([3, 6, 12, 2], 2, [1, 3, 2, 1]) // returns an array of two of the values in the array with weighted distribution
39 * math.pickRandom([3, 6, 12, 2], [1, 3, 2, 1], 2) // returns an array of two of the values in the array with weighted distribution
40 *
41 * See also:
42 *
43 * random, randomInt
44 *
45 * @param {Array | Matrix} array A one dimensional array
46 * @param {Int} number An int or float
47 * @param {Array | Matrix} weights An array of ints or floats
48 * @return {number | Array} Returns a single random value from array when number is 1 or undefined.
49 * Returns an array with the configured number of elements when number is > 1.
50 */
51 return typed({
52 'Array | Matrix': function (possibles) {
53 return _pickRandom(possibles)
54 },
55
56 'Array | Matrix, number': function (possibles, number) {
57 return _pickRandom(possibles, number, undefined)
58 },
59
60 'Array | Matrix, Array': function (possibles, weights) {
61 return _pickRandom(possibles, undefined, weights)
62 },
63
64 'Array | Matrix, Array | Matrix, number': function (possibles, weights, number) {
65 return _pickRandom(possibles, number, weights)
66 },
67
68 'Array | Matrix, number, Array | Matrix': function (possibles, number, weights) {
69 return _pickRandom(possibles, number, weights)
70 }
71 })
72
73 function _pickRandom (possibles, number, weights) {
74 const single = (typeof number === 'undefined')
75 if (single) {
76 number = 1
77 }
78
79 possibles = possibles.valueOf() // get Array
80 if (weights) {
81 weights = weights.valueOf() // get Array
82 }
83
84 if (arraySize(possibles).length > 1) {
85 throw new Error('Only one dimensional vectors supported')
86 }
87
88 let totalWeights = 0
89
90 if (typeof weights !== 'undefined') {
91 if (weights.length !== possibles.length) {
92 throw new Error('Weights must have the same length as possibles')
93 }
94
95 for (let i = 0, len = weights.length; i < len; i++) {
96 if (!isNumber(weights[i]) || weights[i] < 0) {
97 throw new Error('Weights must be an array of positive numbers')
98 }
99
100 totalWeights += weights[i]
101 }
102 }
103
104 const length = possibles.length
105
106 if (length === 0) {
107 return []
108 } else if (number >= length) {
109 return number > 1 ? possibles : possibles[0]
110 }
111
112 const result = []
113 let pick
114
115 while (result.length < number) {
116 if (typeof weights === 'undefined') {
117 pick = possibles[Math.floor(rng() * length)]
118 } else {
119 let randKey = rng() * totalWeights
120
121 for (let i = 0, len = possibles.length; i < len; i++) {
122 randKey -= weights[i]
123
124 if (randKey < 0) {
125 pick = possibles[i]
126 break
127 }
128 }
129 }
130
131 if (result.indexOf(pick) === -1) {
132 result.push(pick)
133 }
134 }
135
136 return single ? result[0] : result
137
138 // TODO: return matrix when input was a matrix
139 // TODO: add support for multi dimensional matrices
140 }
141})