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 -->
+