time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

factory.js (8477B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 'use strict';
     20 
     21 // MODULES //
     22 
     23 var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
     24 var isArrayLike = require( '@stdlib/assert/is-array-like' );
     25 var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
     26 var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
     27 var randu = require( './../../base/mt19937' ).factory;
     28 var copy = require( '@stdlib/utils/copy' );
     29 var discreteUniform = require( './discrete_uniform.js' );
     30 var renormalizing = require( './renormalizing.js' );
     31 var fisherYates = require( './fisher_yates.js' );
     32 var vose = require( './vose.js' );
     33 var defaults = require( './defaults.json' );
     34 var validate = require( './validate.js' );
     35 
     36 
     37 // FUNCTIONS //
     38 
     39 var slice = Array.prototype.slice;
     40 
     41 
     42 // MAIN //
     43 
     44 /**
     45 * Returns a function to sample elements from an array-like object.
     46 *
     47 * @param {(ArrayLike|TypedArrayLike)} [pool] - array-like object from which to sample
     48 * @param {Options} [options] - function options
     49 * @param {PositiveInteger} [options.seed] - integer-valued seed
     50 * @param {NonNegativeInteger} [options.size] - sample size
     51 * @param {boolean} [options.replace=true] - boolean indicating whether to sample with replacement
     52 * @param {boolean} [options.mutate=false] - boolean indicating whether to mutate the `pool` when sampling without replacement
     53 * @throws {TypeError} `pool` must be an array-like object
     54 * @throws {TypeError} options argument must be an object
     55 * @throws {TypeError} must provide valid options
     56 * @returns {Function} function to sample elements from an array-like object
     57 *
     58 * @example
     59 * var sample = factory({
     60 *     'seed': 232
     61 * });
     62 * var out = sample( 'abcdefg' );
     63 * // e.g., returns [ 'g', 'd', 'g', 'f', 'c', 'e', 'f' ]
     64 *
     65 * @example
     66 * var sample = factory( [ 1, 2, 3, 4, 5, 6 ], {
     67 *     'seed': 232,
     68 *     'size': 2
     69 * });
     70 * var out = sample();
     71 * // e.g., returns [ 6, 4 ]
     72 *
     73 * out = sample();
     74 * // e.g., returns [ 6, 5 ]
     75 *
     76 * @example
     77 * var sample = factory( [ 1, 2, 3, 4, 5, 6 ], {
     78 *     'seed': 474,
     79 *     'size': 3,
     80 *     'mutate': true,
     81 *     'replace': false
     82 * });
     83 * var out = sample();
     84 * // e.g., returns [ 4, 3, 6 ]
     85 *
     86 * out = sample();
     87 * // e.g., returns [ 1, 5, 2 ]
     88 *
     89 * out = sample();
     90 * // returns null
     91 *
     92 * @example
     93 * var sample = factory( [ 0, 1 ], {
     94 *     'size': 2
     95 * });
     96 *
     97 * var out = sample();
     98 * // e.g., returns [ 1, 1 ]
     99 *
    100 * out = sample({
    101 *     'size': 10
    102 * });
    103 * // e.g., returns [ 0, 1, 1, 1, 0, 1, 0, 0, 1, 1 ]
    104 *
    105 * @example
    106 * var sample = factory( [ 0, 1 ], {
    107 *     'size': 2
    108 * });
    109 *
    110 * var out = sample();
    111 * // e.g., returns [ 1, 1 ]
    112 *
    113 * out = sample({
    114 *     'replace': false
    115 * });
    116 * // e.g., returns [ 0, 1 ] or [ 1, 0 ]
    117 *
    118 * out = sample();
    119 * // e.g., returns [ 1, 1 ]
    120 *
    121 * @example
    122 * var sample = factory( [ 0, 1 ], {
    123 *     'size': 2,
    124 *     'mutate': true
    125 * });
    126 *
    127 * var out = sample();
    128 * // e.g., returns [ 1, 1 ]
    129 *
    130 * out = sample({
    131 *     'replace': false
    132 * });
    133 * // e.g., returns [ 0, 1 ] or [ 1, 0 ]
    134 *
    135 * out = sample();
    136 * // returns null
    137 */
    138 function factory() {
    139 	var config;
    140 	var pool;
    141 	var conf;
    142 	var rand;
    143 	var err;
    144 	var fcn;
    145 
    146 	conf = copy( defaults );
    147 	if ( arguments.length === 1 ) {
    148 		if ( isArrayLike( arguments[ 0 ] ) || isTypedArrayLike( arguments[ 0 ] ) ) { // eslint-disable-line max-len
    149 			pool = arguments[ 0 ];
    150 		} else {
    151 			config = arguments[ 0 ];
    152 			err = validate( conf, config );
    153 		}
    154 	} else if ( arguments.length > 1 ) {
    155 		pool = arguments[ 0 ];
    156 		config = arguments[ 1 ];
    157 		if ( !( isArrayLike( pool ) || isTypedArrayLike( pool ) ) ) {
    158 			throw new TypeError( 'invalid argument. `pool` argument must be array-like. Value: `' + pool + '`.' );
    159 		}
    160 		err = validate( conf, config );
    161 	}
    162 	if ( err ) {
    163 		throw err;
    164 	}
    165 	if ( config && config.seed ) {
    166 		rand = randu({
    167 			'seed': config.seed
    168 		});
    169 	} else {
    170 		rand = randu();
    171 	}
    172 	if ( pool === void 0 ) {
    173 		fcn = sample1;
    174 	} else {
    175 		if ( isString( pool ) ) {
    176 			pool = pool.split( '' );
    177 		} else {
    178 			pool = copy( pool );
    179 		}
    180 		fcn = sample2;
    181 	}
    182 	setReadOnly( fcn, 'seed', rand.seed );
    183 	setReadOnly( fcn, 'PRNG', rand );
    184 
    185 	rand = rand.normalized;
    186 
    187 	return fcn;
    188 
    189 	/**
    190 	* Samples elements from an array-like object.
    191 	*
    192 	* @private
    193 	* @param {(ArrayLike|TypedArrayLike)} x - array-like object from which to sample elements
    194 	* @param {Options} [options] - function options
    195 	* @param {NonNegativeInteger} [options.size] - sample size
    196 	* @param {ProbabilityArray} [options.probs] - element probabilities
    197 	* @param {boolean} [options.replace=true] - boolean indicating whether to sample with replacement
    198 	* @throws {TypeError} first argument must be array-like
    199 	* @throws {TypeError} options argument must be an object
    200 	* @throws {TypeError} must provide valid options
    201 	* @throws {RangeError} `size` option must be less than or equal to the length of `x` when the `replace` option is `false`
    202 	* @returns {Array} sample
    203 	*/
    204 	function sample1( x, options ) {
    205 		var replace;
    206 		var xcopy;
    207 		var probs;
    208 		var opts;
    209 		var size;
    210 		var err;
    211 
    212 		if ( !( isArrayLike( x ) || isTypedArrayLike( x ) ) ) {
    213 			throw new TypeError( 'invalid argument. First argument must be array-like. Value: `' + x + '`.' );
    214 		}
    215 		if ( isString( x ) ) {
    216 			x = x.split( '' );
    217 		}
    218 		opts = {};
    219 		if ( arguments.length > 1 ) {
    220 			err = validate( opts, options );
    221 			if ( err ) {
    222 				throw err;
    223 			}
    224 		}
    225 		if ( opts.replace === void 0 ) {
    226 			replace = conf.replace;
    227 		} else {
    228 			replace = opts.replace;
    229 		}
    230 		if ( opts.probs !== void 0 ) {
    231 			probs = opts.probs;
    232 		}
    233 		if ( opts.size ) {
    234 			size = opts.size;
    235 		} else if ( conf.size ) {
    236 			size = conf.size;
    237 		} else {
    238 			size = x.length;
    239 		}
    240 		if (
    241 			replace === false &&
    242 			size > x.length
    243 		) {
    244 			throw new RangeError( 'invalid input option. `size` option must be less than or equal to the length of `x` when `replace` is `false`. Option: `' + size + '`.' );
    245 		}
    246 		// Custom probabilities...
    247 		if ( probs ) {
    248 			if ( replace ) {
    249 				return vose( x, size, rand, probs );
    250 			}
    251 			return renormalizing( x, size, rand, probs );
    252 		}
    253 		// All elements equally likely...
    254 		if ( replace ) {
    255 			return discreteUniform( x, size, rand );
    256 		}
    257 		xcopy = slice.call( x );
    258 		return fisherYates( xcopy, size, rand );
    259 	}
    260 
    261 	/**
    262 	* Samples elements from a population.
    263 	*
    264 	* @private
    265 	* @param {Options} [options] - function options
    266 	* @param {NonNegativeInteger} [options.size] - sample size
    267 	* @param {boolean} [options.replace=true] - boolean indicating whether to sample with replacement
    268 	* @param {boolean} [options.mutate=false] - boolean indicating whether to mutate the `pool` when sampling without replacement
    269 	* @throws {TypeError} options argument must be an object
    270 	* @throws {TypeError} must provide valid options
    271 	* @throws {RangeError} `size` option must be less than or equal to the population when the `replace` option is `false`
    272 	* @returns {Array} sample
    273 	*/
    274 	function sample2( options ) {
    275 		var replace;
    276 		var mutate;
    277 		var opts;
    278 		var size;
    279 		var err;
    280 		var out;
    281 
    282 		if ( pool.length === 0 ) {
    283 			return null;
    284 		}
    285 		opts = {};
    286 		if ( arguments.length ) {
    287 			err = validate( opts, options );
    288 			if ( err ) {
    289 				throw err;
    290 			}
    291 		}
    292 		if ( opts.mutate === void 0 ) {
    293 			mutate = conf.mutate;
    294 		} else {
    295 			mutate = opts.mutate;
    296 		}
    297 		if ( opts.replace === void 0 ) {
    298 			replace = conf.replace;
    299 		} else {
    300 			replace = opts.replace;
    301 		}
    302 		if ( opts.size ) {
    303 			size = opts.size;
    304 		} else if ( conf.size ) {
    305 			size = conf.size;
    306 		} else {
    307 			size = pool.length;
    308 		}
    309 		if (
    310 			replace === false &&
    311 			size > pool.length
    312 		) {
    313 			throw new RangeError( 'invalid input option. `size` option must be less than or equal to the population size when `replace` is `false`. Option: `' + size + '`.' );
    314 		}
    315 		if ( replace ) {
    316 			return discreteUniform( pool, size, rand );
    317 		}
    318 		out = fisherYates( pool, size, rand );
    319 		if ( mutate ) {
    320 			// Remove the sample observations:
    321 			pool = pool.slice( size, pool.length );
    322 		}
    323 		return out;
    324 	}
    325 }
    326 
    327 
    328 // EXPORTS //
    329 
    330 module.exports = factory;