/** * @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;