Added RandomAllInclusiveSet (ideal algorithm for random testing)

master
Mike Gerwitz 2012-03-04 08:50:32 -05:00
parent 31b7fb8e44
commit 02db399e56
4 changed files with 109 additions and 34 deletions

View File

@ -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 );
}
} );

View File

@ -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;
}
} );

View File

@ -8,7 +8,7 @@ rectest.set.SetFactory = Class( 'SetFactory',
var sets = rectest.set;
return [
null,
sets.RandomAllInclusiveSet,
sets.RandomGroupedSet,
null,
null

View File

@ -105,6 +105,7 @@
development purposes -->
<script type="text/javascript" src="scripts/set/Set.js"></script>
<script type="text/javascript" src="scripts/set/RandomGroupedSet.js"></script>
<script type="text/javascript" src="scripts/set/RandomAllInclusiveSet.js"></script>
<script type="text/javascript" src="scripts/set/SetFactory.js"></script>
<script type="text/javascript" src="scripts/TestCase.js"></script>
<script type="text/javascript" src="scripts/TestRun.js"></script>