From 02db399e56fdd7fcc26c0bdbb786809cdb027bc9 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sun, 4 Mar 2012 08:50:32 -0500 Subject: [PATCH] Added RandomAllInclusiveSet (ideal algorithm for random testing) --- scripts/set/RandomAllInclusiveSet.js | 84 ++++++++++++++++++++++++++++ scripts/set/RandomGroupedSet.js | 56 ++++++++----------- scripts/set/SetFactory.js | 2 +- test.html | 1 + 4 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 scripts/set/RandomAllInclusiveSet.js diff --git a/scripts/set/RandomAllInclusiveSet.js b/scripts/set/RandomAllInclusiveSet.js new file mode 100644 index 0000000..1911b61 --- /dev/null +++ b/scripts/set/RandomAllInclusiveSet.js @@ -0,0 +1,84 @@ +/** + * Represents a set that randomizes all elements of an entire predicted set, + * ensuring that repeat sets are less predictable. + * + * This algorithm works by first repeating the given set S the necessary number + * of times in order to produce a new set S' that contains at least one element + * for the requested sample size. S' is then randomized in its entirety. + * + * See also RandomGroupedSet. + */ +rectest.set.RandomAllInclusiveSet = Class( 'RandomAllInclusiveSet' ) + .extend( rectest.set.RandomGroupedSet, +{ + /** + * Randomizes an entire set large enough to encompass the requested sample + * size + * + * The actual array randomization algorithm is inherited from the parent. + * + * @param {Array} set set to randomize + * + * @return {Array} randomized set + */ + 'override protected randomizeSet': function( set ) + { + var samples = this.getSampleSize(); + + // this algorithm will work only if we know how many samples are desired + if ( samples < 1 ) + { + throw Error( + "RandomAllInclusiveSet can only be used with sets of " + + "known length; desired sample size is unknown" + ); + } + + // nothing we can do with an empty set (avoid the division by 0 and just + // bail out) + if ( set.length === 0 ) + { + return set; + } + + // call the super method with the set repeated N times, where N is the + // minimum number of repeats required to satisfy the requested sample + // size (may result in a set larger than the sample size, but that is + // fine) + return this.__super( this._repeatSet( set, + Math.ceil( samples / set.length ) + ) ); + }, + + + /** + * Recursively repeats the given set n times + * + * @param {Array} set set to repeat + * @param {number} n number of times to repeat + * @param {=Array} s2 destination array (defaults to [] on first iteration) + * + * @return {Array} s2 after operation is complete + */ + 'private _repeatSet': function( set, n, s2 ) + { + s2 = s2 || []; + n = ( isNaN( n ) ) ? 0 : n; // failsafe + + var i = set.length; + + if ( n === 0 ) + { + return s2; + } + + // does not matter that we're doing it in reverse, since the set + // will be randomized later + while ( i-- ) + { + s2.push( set[ i ] ); + } + + return this._repeatSet( set, --n, s2 ); + } +} ); diff --git a/scripts/set/RandomGroupedSet.js b/scripts/set/RandomGroupedSet.js index 6bc7534..ee24550 100644 --- a/scripts/set/RandomGroupedSet.js +++ b/scripts/set/RandomGroupedSet.js @@ -2,6 +2,10 @@ * Represents a set that is randomized each time the first element is reached. * The set will loop back to the first element until the required number of * samples are satisfied. + * + * Note that this means that, should the sample size be larger than the number + * of elements in the set, the set will be randomized only for each iteration, + * which may not be what you want for a truely random sample set. */ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) .implement( rectest.set.Set ) @@ -35,48 +39,25 @@ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) __construct: function( base_set, sample_size ) { this._set = base_set; - this._setLength = this._set.length; this._samplesRemain = +sample_size || -1; this._reset(); }, - 'private _repeatSet': function( set, n, s2 ) - { - s2 = s2 || []; - - var i = set.length; - - if ( n === 0 ) - { - return s2; - } - - // does not matter that we're doing it in reverse, since the set - // will be randomized later - while ( i-- ) - { - s2[ i ] = set[ i ]; - } - - return this._repeatSet( set, --n, s2 ); - }, - - /** * Randomizes a set using the Fisher-Yates shuffle algorithm * * @param {Array} set set to sort */ - 'private _randomizeSet': function( set ) + 'virtual protected randomizeSet': function( set ) { var i = set.length; // simply prevent an infinite loop below if ( i === 0 ) { - return; + return set; } // simply loop through each element in the set, swapping each with a @@ -86,6 +67,8 @@ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) var r = Math.floor( Math.random() * ( i + 1 ) ); set[ i ] = [ set[ r ], ( set[ r ] = set[ i ] ) ][ 0 ]; } + + return set; }, @@ -96,12 +79,6 @@ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) */ 'public getNextElement': function() { - // reset once we're at the end - if ( this._setPos === this._setLength ) - { - this._reset(); - } - // return null once we have returned all of the requested samples // (intentionally a 0 check, as this will be negative for an // infinite number of samples) @@ -110,6 +87,12 @@ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) return null; } + // reset once we're at the end + if ( this._setPos === this._setLength ) + { + this._reset(); + } + // return element from set (do not shift/pop, since we may need to // repeatedly iterate through this list) return this._set[ this._setPos++ ]; @@ -118,7 +101,14 @@ rectest.set.RandomGroupedSet = Class( 'RandomGroupedSet' ) 'private _reset': function() { - this._setPos = 0; - this._randomizeSet( this._set ); + this._setPos = 0; + this._set = this.randomizeSet( this._set ); + this._setLength = this._set.length; + }, + + + 'protected getSampleSize': function() + { + return this._samplesRemain; } } ); diff --git a/scripts/set/SetFactory.js b/scripts/set/SetFactory.js index 34b66fa..6c259ab 100644 --- a/scripts/set/SetFactory.js +++ b/scripts/set/SetFactory.js @@ -8,7 +8,7 @@ rectest.set.SetFactory = Class( 'SetFactory', var sets = rectest.set; return [ - null, + sets.RandomAllInclusiveSet, sets.RandomGroupedSet, null, null diff --git a/test.html b/test.html index 61393cb..6455d88 100644 --- a/test.html +++ b/test.html @@ -105,6 +105,7 @@ development purposes --> +