165 lines
4.1 KiB
JavaScript
165 lines
4.1 KiB
JavaScript
|
/**
|
||
|
* @license Apache-2.0
|
||
|
*
|
||
|
* Copyright (c) 2018 The Stdlib Authors.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
// MODULES //
|
||
|
|
||
|
var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
|
||
|
var isArrayLike = require( '@stdlib/assert/is-array-like' );
|
||
|
var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
|
||
|
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
|
||
|
var deepCopy = require( '@stdlib/utils/copy' );
|
||
|
var floor = require( '@stdlib/math/base/special/floor' );
|
||
|
var randu = require( './../../base/mt19937' ).factory;
|
||
|
var defaults = require( './defaults.json' );
|
||
|
var validate = require( './validate.js' );
|
||
|
|
||
|
|
||
|
// MAIN //
|
||
|
|
||
|
/**
|
||
|
* Returns a function to create a random permutation of elements from an array-like object.
|
||
|
*
|
||
|
* @param {Options} [config] - function options
|
||
|
* @param {PositiveInteger} [config.seed] - integer-valued seed
|
||
|
* @param {string} [config.copy="shallow"] - default copy option (`deep`, `shallow` or `none`)
|
||
|
* @throws {TypeError} options argument must be an object
|
||
|
* @returns {Function} shuffle function
|
||
|
*
|
||
|
* @example
|
||
|
* var shuffle = factory({
|
||
|
* 'seed': 249
|
||
|
* });
|
||
|
* var data = [ 3, 8, 4, 8 ];
|
||
|
* var out = shuffle( data );
|
||
|
* // e.g., returns [ 4, 3, 8, 8 ]
|
||
|
*/
|
||
|
function factory( config ) {
|
||
|
var conf;
|
||
|
var rand;
|
||
|
var err;
|
||
|
|
||
|
conf = deepCopy( defaults );
|
||
|
if ( arguments.length ) {
|
||
|
err = validate( conf, config );
|
||
|
if ( err ) {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
if ( config && config.seed ) {
|
||
|
rand = randu({
|
||
|
'seed': config.seed
|
||
|
});
|
||
|
} else {
|
||
|
rand = randu();
|
||
|
}
|
||
|
setReadOnly( shuffle, 'seed', rand.seed );
|
||
|
setReadOnly( shuffle, 'PRNG', rand );
|
||
|
|
||
|
rand = rand.normalized;
|
||
|
|
||
|
return shuffle;
|
||
|
|
||
|
/**
|
||
|
* Returns a random permutation of elements in `arr`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {(ArrayLike|TypedArrayLike)} arr - array-like object to shuffle
|
||
|
* @param {Options} [options] - function options
|
||
|
* @param {string} [options.copy] - string indicating whether to return a copy (`deep`,`shallow` or `none`)
|
||
|
* @throws {TypeError} first argument must be array-like
|
||
|
* @throws {TypeError} `options` must be an object
|
||
|
* @throws {TypeError} must provide valid options
|
||
|
* @returns {ArrayLike} the shuffled array-like object
|
||
|
*
|
||
|
* @example
|
||
|
* var data = [ 1, 2, 3 ];
|
||
|
* var out = shuffle( data );
|
||
|
* // e.g., returns [ 3, 1, 2 ]
|
||
|
*
|
||
|
* @example
|
||
|
* var data = [ 1, 2, 3 ];
|
||
|
* var out = shuffle( data, {
|
||
|
* 'copy': 'none'
|
||
|
* });
|
||
|
* var bool = ( data === out );
|
||
|
* // returns true
|
||
|
*/
|
||
|
function shuffle( arr, options ) {
|
||
|
var strflg;
|
||
|
var level;
|
||
|
var copy;
|
||
|
var opts;
|
||
|
var err;
|
||
|
var out;
|
||
|
var tmp;
|
||
|
var N;
|
||
|
var i;
|
||
|
var j;
|
||
|
|
||
|
if ( !( isArrayLike( arr ) || isTypedArrayLike( arr ) ) ) {
|
||
|
throw new TypeError( 'invalid argument. First argument must be array-like. Value: `' + arr + '`.' );
|
||
|
}
|
||
|
if ( arguments.length > 1 ) {
|
||
|
opts = {};
|
||
|
err = validate( opts, options );
|
||
|
if ( err ) {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
copy = ( opts && opts.copy ) ? opts.copy : conf.copy;
|
||
|
|
||
|
strflg = isString( arr );
|
||
|
if ( strflg ) {
|
||
|
arr = arr.split( '' );
|
||
|
copy = 'none';
|
||
|
}
|
||
|
|
||
|
level = 0;
|
||
|
if ( copy === 'shallow' ) {
|
||
|
level += 1;
|
||
|
} else if ( copy === 'deep' ) {
|
||
|
level += 2;
|
||
|
}
|
||
|
N = arr.length;
|
||
|
out = deepCopy( arr, level );
|
||
|
|
||
|
// Note: we skip the first element, as no further swaps are possible given that all other indices are excluded from swapping...
|
||
|
for ( i = N - 1; i > 0; i-- ) {
|
||
|
// Generate an integer index on the interval [0,i]:
|
||
|
j = floor( rand() * (i+1.0) );
|
||
|
|
||
|
// Swap elements:
|
||
|
tmp = out[ i ];
|
||
|
out[ i ] = out[ j ];
|
||
|
out[ j ] = tmp;
|
||
|
}
|
||
|
|
||
|
if ( strflg ) {
|
||
|
out = arr.join( '' );
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// EXPORTS //
|
||
|
|
||
|
module.exports = factory;
|