UNPKG

7.96 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.randint = randint;
7exports.rand = rand;
8exports.sampleFisherYates = sampleFisherYates;
9exports.sample = sample;
10
11var _arrays = require('../../arrays');
12
13var Arrays = _interopRequireWildcard(_arrays);
14
15function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
16
17function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // Local imports
18
19
20/**
21 * Generate a random integer between a lower bound (inclusive) and an upper bound (exclusive).
22 *
23 * @param {number} a - Lower bound (inclusive)
24 * @param {number} b - Upper bound (exclusive)
25 * @param {number|Array.<number>} [shape = null] - If null, a single element is returned. If
26 * integer, an array of {shape} elements is returned. If an Array, random numbers are returned in
27 * a shape specified by this array. n-th element corresponds to the number of elements in the
28 * n-th dimension.
29 * @return {Array.<mixed>} Array of the specified with zero in all entries
30 * @return {number|Array.<mixed>} Random integer in range [a,b), or a possibly nested array of
31 * random integers in range [a, b)
32 */
33function randint(a, b) {
34 var shape = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
35
36 if (Number.isInteger(shape)) {
37 // List of random integers
38 return [].concat(_toConsumableArray(Array(shape))).map(function (x) {
39 return randint(a, b);
40 });
41 } else if (Array.isArray(shape) && shape.length > 0) {
42 if (shape.length === 1) {
43 // Single shape item remaining; return list of integers
44 return randint(a, b, shape[0]);
45 }
46
47 // Nested list of random integers
48 return [].concat(_toConsumableArray(Array(shape[0]))).map(function () {
49 return randint(a, b, shape.slice(1));
50 });
51 }
52
53 // Single random integer
54 return a + Math.floor((b - a) * Math.random());
55}
56
57/**
58 * Generate a random number between a lower bound (inclusive) and an upper bound (exclusive).
59 *
60 * @param {number} a - Lower bound (inclusive)
61 * @param {number} b - Upper bound (exclusive)
62 * @return {number} Random number in range [a,b)
63 */
64function rand(a, b) {
65 return a + Math.random() * (b - a);
66}
67
68/**
69 * Take a random sample without replacement from an array. Uses the Fisher-Yates shuffling,
70 * algorithm, modified to accomodate sampling.
71 *
72 * @param {Array.<mixed>} input Input array
73 * @param {number} number Number of elements to sample from the input array
74 * @return {Array.<mixed>} Array of length {number} with values sampled from the input array
75 */
76function sampleFisherYates(input, number) {
77 // Copy input array
78 var shuffledArray = input.slice(0);
79
80 // Number of elements in the input array
81 var numElements = input.length;
82
83 for (var i = numElements - 1; i >= numElements - number; i -= 1) {
84 var index = Random.randint(0, i + 1);
85 var tmp = shuffledArray[index];
86 shuffledArray[index] = shuffledArray[i];
87 shuffledArray[i] = tmp;
88 }
89
90 // Return the sampled values
91 return shuffledArray.slice(numElements - number);
92}
93
94/**
95 * Take a random sample with or without replacement from an array with uniform weights.
96 *
97 * @param {Array.<mixed>} input - Input array
98 * @param {number} number - Number of elements to sample from the input array
99 * @param {boolean} [withReplacement=true] - Whether to sample with (set to true) or without
100 * replacement (false)
101 * @return {Array.<mixed>} Array of length {number} with values sampled from the input array
102 */
103function sampleUniform(input, number) {
104 var withReplacement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
105
106 // If sampling without replacement, use Fisher-Yates sampling
107 if (!withReplacement) {
108 return sampleFisherYates(input, number);
109 }
110
111 // If sampling with replacement, choose a random element each time
112 var indices = Random.randint(0, input.length, number);
113 return indices.map(function (x) {
114 return input[x];
115 });
116}
117
118/**
119 * Take a random sample with or without replacement from an array. Supports using sampling weights,
120 * governing the probability of an item in the input array being selected.
121 *
122 * @param {Array.<mixed>} input - Input array
123 * @param {number} number - Number of elements to sample from the input array
124 * @param {boolean} [withReplacement=true] - Whether to sample with (set to true) or without
125 * replacement (false)
126 * @param {Array.<number>|string} [weights='uniform'] - Weights to use for sampling. Defaults to
127 * 'uniform', which means all samples have equal probability of being selected. Alternatively, you
128 * can pass an array of weights, containing a single weight for each element in the input array
129 * @return {Array.<mixed>} Array of length {number} with values sampled from the input array
130 */
131function sample(input, number) {
132 var withReplacement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
133 var weights = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'uniform';
134
135 if (Array.isArray(weights)) {
136 if (weights.length !== input.length) {
137 throw new Error('Weights array length does not equal input array length.');
138 }
139
140 if (!withReplacement && number > weights.filter(function (x) {
141 return x > 0;
142 }).length) {
143 throw new Error('Invalid sampling quantity specified: sampling without replacement cannot sample more elements than the number of non-zero weights in the weights array.');
144 }
145 } else if (weights !== 'uniform') {
146 throw new Error('Invalid value specified for "weights".');
147 }
148
149 if (!withReplacement && number > input.length) {
150 throw new Error('Invalid sampling quantity specified: sampling without replacement cannot sample more elements than the number of input elements.');
151 }
152
153 // Use the uniform sampling method if the user has specified uniform weights
154 if (weights === 'uniform') {
155 return sampleUniform(input, number, withReplacement);
156 }
157
158 // Copy weights vector
159 var useWeights = weights.slice();
160
161 var calculateCumWeights = function calculateCumWeights(localWeights) {
162 return localWeights.reduce(function (r, a, i) {
163 return [].concat(_toConsumableArray(r), [i > 0 ? r[i - 1] + a : a]);
164 }, []);
165 };
166
167 var sampleSingle = function sampleSingle(useCumWeights) {
168 // Generate a random number, and find the interval in the array of cumulative weights to which
169 // it corresponds. We use this index as the sampled array index, which corresponds to weighted
170 // sampling
171 var randomNumber = Random.rand(0, useCumWeights[useCumWeights.length - 1]);
172 return Search.binaryIntervalSearch([0].concat(_toConsumableArray(useCumWeights)), randomNumber);
173 };
174
175 if (withReplacement) {
176 // Sample with replacement, i.e., randomly sample one element n times in a row
177 var cumWeights = calculateCumWeights(useWeights);
178
179 return Arrays.zeros(number).map(function () {
180 return input[sampleSingle(cumWeights)];
181 });
182 }
183
184 // Sample without replacement: randomly sample one element, remove it from the list of elements
185 // that can still be sampled and the weights list, and sample from the remaining elements
186
187 // Copy input
188 var useInput = input.slice();
189
190 // List of elements sampled
191 var samples = [];
192
193 while (samples.length < number) {
194 // Sample from remaining inputs
195 var _cumWeights = calculateCumWeights(useWeights);
196 var useSample = sampleSingle(_cumWeights);
197 samples.push(useInput[useSample]);
198
199 // Remove sampled element from input elements and weights lists
200 useInput.splice(useSample, 1);
201 useWeights.splice(useSample, 1);
202 }
203
204 return samples;
205}
\No newline at end of file