factory.js (4160B)
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 deepCopy = require( '@stdlib/utils/copy' ); 28 var floor = require( '@stdlib/math/base/special/floor' ); 29 var randu = require( './../../base/mt19937' ).factory; 30 var defaults = require( './defaults.json' ); 31 var validate = require( './validate.js' ); 32 33 34 // MAIN // 35 36 /** 37 * Returns a function to create a random permutation of elements from an array-like object. 38 * 39 * @param {Options} [config] - function options 40 * @param {PositiveInteger} [config.seed] - integer-valued seed 41 * @param {string} [config.copy="shallow"] - default copy option (`deep`, `shallow` or `none`) 42 * @throws {TypeError} options argument must be an object 43 * @returns {Function} shuffle function 44 * 45 * @example 46 * var shuffle = factory({ 47 * 'seed': 249 48 * }); 49 * var data = [ 3, 8, 4, 8 ]; 50 * var out = shuffle( data ); 51 * // e.g., returns [ 4, 3, 8, 8 ] 52 */ 53 function factory( config ) { 54 var conf; 55 var rand; 56 var err; 57 58 conf = deepCopy( defaults ); 59 if ( arguments.length ) { 60 err = validate( conf, config ); 61 if ( err ) { 62 throw err; 63 } 64 } 65 if ( config && config.seed ) { 66 rand = randu({ 67 'seed': config.seed 68 }); 69 } else { 70 rand = randu(); 71 } 72 setReadOnly( shuffle, 'seed', rand.seed ); 73 setReadOnly( shuffle, 'PRNG', rand ); 74 75 rand = rand.normalized; 76 77 return shuffle; 78 79 /** 80 * Returns a random permutation of elements in `arr`. 81 * 82 * @private 83 * @param {(ArrayLike|TypedArrayLike)} arr - array-like object to shuffle 84 * @param {Options} [options] - function options 85 * @param {string} [options.copy] - string indicating whether to return a copy (`deep`,`shallow` or `none`) 86 * @throws {TypeError} first argument must be array-like 87 * @throws {TypeError} `options` must be an object 88 * @throws {TypeError} must provide valid options 89 * @returns {ArrayLike} the shuffled array-like object 90 * 91 * @example 92 * var data = [ 1, 2, 3 ]; 93 * var out = shuffle( data ); 94 * // e.g., returns [ 3, 1, 2 ] 95 * 96 * @example 97 * var data = [ 1, 2, 3 ]; 98 * var out = shuffle( data, { 99 * 'copy': 'none' 100 * }); 101 * var bool = ( data === out ); 102 * // returns true 103 */ 104 function shuffle( arr, options ) { 105 var strflg; 106 var level; 107 var copy; 108 var opts; 109 var err; 110 var out; 111 var tmp; 112 var N; 113 var i; 114 var j; 115 116 if ( !( isArrayLike( arr ) || isTypedArrayLike( arr ) ) ) { 117 throw new TypeError( 'invalid argument. First argument must be array-like. Value: `' + arr + '`.' ); 118 } 119 if ( arguments.length > 1 ) { 120 opts = {}; 121 err = validate( opts, options ); 122 if ( err ) { 123 throw err; 124 } 125 } 126 copy = ( opts && opts.copy ) ? opts.copy : conf.copy; 127 128 strflg = isString( arr ); 129 if ( strflg ) { 130 arr = arr.split( '' ); 131 copy = 'none'; 132 } 133 134 level = 0; 135 if ( copy === 'shallow' ) { 136 level += 1; 137 } else if ( copy === 'deep' ) { 138 level += 2; 139 } 140 N = arr.length; 141 out = deepCopy( arr, level ); 142 143 // Note: we skip the first element, as no further swaps are possible given that all other indices are excluded from swapping... 144 for ( i = N - 1; i > 0; i-- ) { 145 // Generate an integer index on the interval [0,i]: 146 j = floor( rand() * (i+1.0) ); 147 148 // Swap elements: 149 tmp = out[ i ]; 150 out[ i ] = out[ j ]; 151 out[ j ] = tmp; 152 } 153 154 if ( strflg ) { 155 out = arr.join( '' ); 156 } 157 return out; 158 } 159 } 160 161 162 // EXPORTS // 163 164 module.exports = factory;