Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* Example output:
- hi.range(0,10).shuffle().array()
- shuffleRange.js:27 Picked value no. 4 of 10 : 4
- shuffleRange.js:94 Unpicked values: (9) [0, 1, 2, 3, 5, 6, 7, 8, 9]
- shuffleRange.js:99 History: [4]
- shuffleRange.js:100 Picked value no. 9 of 9 : 9
- shuffleRange.js:94 Unpicked values: (8) [0, 1, 2, 3, 5, 6, 7, 8]
- shuffleRange.js:99 History: (2) [4, 9]
- shuffleRange.js:100 Picked value no. 3 of 8 : 3
- shuffleRange.js:94 Unpicked values: (7) [0, 1, 2, 5, 6, 7, 8]
- shuffleRange.js:99 History: (3) [4, 9, 3]
- shuffleRange.js:100 Picked value no. 1 of 7 : 1
- shuffleRange.js:94 Unpicked values: (6) [0, 2, 5, 6, 7, 8]
- shuffleRange.js:99 History: (4) [4, 3, 1, 9]
- shuffleRange.js:100 Picked value no. 7 of 6 : 7
- shuffleRange.js:94 Unpicked values: (5) [0, 2, 5, 6, 8]
- shuffleRange.js:99 History: (5) [3, 1, 4, 7, 9]
- shuffleRange.js:100 Picked value no. 8 of 5 : 8
- shuffleRange.js:94 Unpicked values: (4) [0, 2, 5, 6]
- shuffleRange.js:99 History: (6) [1, 3, 4, 7, 8, 9]
- shuffleRange.js:100 Picked value no. 5 of 4 : 5
- shuffleRange.js:94 Unpicked values: (3) [0, 2, 6]
- shuffleRange.js:99 History: (7) [1, 3, 4, 7, 8, 5, 9]
- shuffleRange.js:100 Picked value no. 2 of 3 : 2
- shuffleRange.js:94 Unpicked values: (2) [0, 6]
- shuffleRange.js:99 History: (8) [1, 3, 4, 7, 8, 5, 9, 2]
- shuffleRange.js:100 Picked value no. 0 of 2 : 0
- shuffleRange.js:94 Unpicked values: [6]
- shuffleRange.js:99 History: (9) [1, 3, 4, 5, 2, 0, 7, 8, 9]
- shuffleRange.js:100 Picked value no. 6 of 1 : 6
- (10) [4, 9, 3, 1, 7, 8, 5, 2, 0, 6]
- */
- // This is the functional equivalent of shuffling a sequence created using
- // the range function with a nonzero step.
- // This constructor is used in range sequences' own shuffle methods.
- hi.ShuffleNumberRangeSequence = function(
- random, start, end, step, valueHistory = undefined
- ){
- if(step === 0){
- throw "Failed to create sequence: Step must be nonzero.";
- }
- this.random = random;
- this.start = start;
- this.end = end;
- this.step = step;
- if(start <= end){
- this.lowValue = start;
- this.highValue = end;
- }else{
- this.lowValue = end;
- this.highValue = start;
- }
- this.totalValues = Math.floor(
- (this.highValue - this.lowValue) / step
- );
- this.valueHistory = valueHistory || [
- Math.floor(this.random() * this.totalValues)
- ];
- console.log(
- "Picked value no.",this.valueHistory[0]," of ",this.totalValues, ":",
- this.valueHistory[0]
- );
- };
- hi.ShuffleNumberRangeSequence.prototype = Object.create(hi.Sequence.prototype);
- Object.assign(hi.ShuffleNumberRangeSequence.prototype, {
- bounded: () => true,
- done: function(){
- return this.valueHistory.length > this.totalValues;
- },
- length: function(){
- return this.totalValues;
- },
- left: function(){
- return this.totalValues - this.valueHistory.length;
- },
- front: function(){
- return this.start + (
- this.step * this.valueHistory[this.valueHistory.length - 1]
- );
- },
- popFront: function(){
- // Because this algorithm warrants some explanation:
- // 1. This method is called, requesting a new number.
- // 2. If enough numbers have already been generated do no more steps,
- // just push undefined to the history array. This matters because it
- // handles the case where popFront pops the last element.
- // 2. i is set to some random number in the range [0, remaining elements).
- // 3. All previously generated numbers are enumerated. While i is less
- // than or equal to any of those numbers, i is incremented and then the
- // numbers are enumerated repeatedly until i isn't incremented again.
- // This operation is the same as taking index i of the sequence of
- // numbers that haven't been chosen yet.
- // 3a. During step 3, the array containing previously generated numbers
- // is bubble sorted; the closer the array's contents are to being
- // entirely in ascending order, the fewer times step 3 will need to
- // enumerate the history array the next time this method is called.
- // In essence: Whenever a pair of elements are encountered that are
- // relatively in descending order, they are swapped to instead be
- // in ascending order.
- // TODO: At what point, if any, does this stop being more performant
- // than a Fisher-Yates shuffle? Can a heuristic be used to choose
- // which algorithm to use depending on the length of the input?
- if(this.valueHistory.length < this.totalValues){
- let i = Math.floor(this.random() * (
- this.totalValues - this.valueHistory.length
- ));
- let j = i;
- for(const value of this.valueHistory){
- if(value <= i) i++;
- }
- while(i !== j){
- j = i;
- if(this.valueHistory[0] >= j && this.valueHistory[0] <= i) i++;
- for(let k = 1; k < this.valueHistory.length; k++){
- const value = this.valueHistory[k];
- // Increment i where appropriate
- if(value >= j && value <= i) i++;
- // Ensure that values appear in ascending order
- if(value < this.valueHistory[k - 1]){
- this.valueHistory[k] = this.valueHistory[k - 1];
- this.valueHistory[k - 1] = value;
- }
- }
- }
- console.log(
- "Unpicked values:", hi.range(this.start,this.end).filter(
- n=>!hi.any(this.valueHistory, x=>x===n)
- ).array()
- );
- console.log("History:", this.valueHistory);
- console.log(
- "Picked value no.", i, " of ", this.left(), ":", j
- );
- this.valueHistory.push(j);
- }else{
- this.valueHistory.push(undefined);
- }
- },
- back: null,
- popBack: null,
- index: null,
- slice: null,
- has: null,
- get: null,
- // Can't copy or reset since result is nondeterministic.
- // TODO: Use resettable RNG objects instead of e.g. Math.random?
- copy: null,
- reset: null,
- });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement