Advertisement
pineapplemachine

Generate list of unique random numbers

Jun 29th, 2017
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* Example output:
  2.  
  3. hi.range(0,10).shuffle().array()
  4. shuffleRange.js:27 Picked value no. 4  of  10 : 4
  5. shuffleRange.js:94 Unpicked values: (9) [0, 1, 2, 3, 5, 6, 7, 8, 9]
  6. shuffleRange.js:99 History: [4]
  7. shuffleRange.js:100 Picked value no. 9  of  9 : 9
  8. shuffleRange.js:94 Unpicked values: (8) [0, 1, 2, 3, 5, 6, 7, 8]
  9. shuffleRange.js:99 History: (2) [4, 9]
  10. shuffleRange.js:100 Picked value no. 3  of  8 : 3
  11. shuffleRange.js:94 Unpicked values: (7) [0, 1, 2, 5, 6, 7, 8]
  12. shuffleRange.js:99 History: (3) [4, 9, 3]
  13. shuffleRange.js:100 Picked value no. 1  of  7 : 1
  14. shuffleRange.js:94 Unpicked values: (6) [0, 2, 5, 6, 7, 8]
  15. shuffleRange.js:99 History: (4) [4, 3, 1, 9]
  16. shuffleRange.js:100 Picked value no. 7  of  6 : 7
  17. shuffleRange.js:94 Unpicked values: (5) [0, 2, 5, 6, 8]
  18. shuffleRange.js:99 History: (5) [3, 1, 4, 7, 9]
  19. shuffleRange.js:100 Picked value no. 8  of  5 : 8
  20. shuffleRange.js:94 Unpicked values: (4) [0, 2, 5, 6]
  21. shuffleRange.js:99 History: (6) [1, 3, 4, 7, 8, 9]
  22. shuffleRange.js:100 Picked value no. 5  of  4 : 5
  23. shuffleRange.js:94 Unpicked values: (3) [0, 2, 6]
  24. shuffleRange.js:99 History: (7) [1, 3, 4, 7, 8, 5, 9]
  25. shuffleRange.js:100 Picked value no. 2  of  3 : 2
  26. shuffleRange.js:94 Unpicked values: (2) [0, 6]
  27. shuffleRange.js:99 History: (8) [1, 3, 4, 7, 8, 5, 9, 2]
  28. shuffleRange.js:100 Picked value no. 0  of  2 : 0
  29. shuffleRange.js:94 Unpicked values: [6]
  30. shuffleRange.js:99 History: (9) [1, 3, 4, 5, 2, 0, 7, 8, 9]
  31. shuffleRange.js:100 Picked value no. 6  of  1 : 6
  32. (10) [4, 9, 3, 1, 7, 8, 5, 2, 0, 6]
  33.  
  34. */
  35.  
  36. // This is the functional equivalent of shuffling a sequence created using
  37. // the range function with a nonzero step.
  38. // This constructor is used in range sequences' own shuffle methods.
  39. hi.ShuffleNumberRangeSequence = function(
  40.     random, start, end, step, valueHistory = undefined
  41. ){
  42.     if(step === 0){
  43.         throw "Failed to create sequence: Step must be nonzero.";
  44.     }
  45.     this.random = random;
  46.     this.start = start;
  47.     this.end = end;
  48.     this.step = step;
  49.     if(start <= end){
  50.         this.lowValue = start;
  51.         this.highValue = end;
  52.     }else{
  53.         this.lowValue = end;
  54.         this.highValue = start;
  55.     }
  56.     this.totalValues = Math.floor(
  57.         (this.highValue - this.lowValue) / step
  58.     );
  59.     this.valueHistory = valueHistory || [
  60.         Math.floor(this.random() * this.totalValues)
  61.     ];
  62.     console.log(
  63.         "Picked value no.",this.valueHistory[0]," of ",this.totalValues, ":",
  64.         this.valueHistory[0]
  65.     );
  66. };
  67.  
  68. hi.ShuffleNumberRangeSequence.prototype = Object.create(hi.Sequence.prototype);
  69. Object.assign(hi.ShuffleNumberRangeSequence.prototype, {
  70.     bounded: () => true,
  71.     done: function(){
  72.         return this.valueHistory.length > this.totalValues;
  73.     },
  74.     length: function(){
  75.         return this.totalValues;
  76.     },
  77.     left: function(){
  78.         return this.totalValues - this.valueHistory.length;
  79.     },
  80.     front: function(){
  81.         return this.start + (
  82.             this.step * this.valueHistory[this.valueHistory.length - 1]
  83.         );
  84.     },
  85.     popFront: function(){
  86.         // Because this algorithm warrants some explanation:
  87.         // 1. This method is called, requesting a new number.
  88.         // 2. If enough numbers have already been generated do no more steps,
  89.         // just push undefined to the history array. This matters because it
  90.         // handles the case where popFront pops the last element.
  91.         // 2. i is set to some random number in the range [0, remaining elements).
  92.         // 3. All previously generated numbers are enumerated. While i is less
  93.         // than or equal to any of those numbers, i is incremented and then the
  94.         // numbers are enumerated repeatedly until i isn't incremented again.
  95.         // This operation is the same as taking index i of the sequence of
  96.         // numbers that haven't been chosen yet.
  97.         // 3a. During step 3, the array containing previously generated numbers
  98.         // is bubble sorted; the closer the array's contents are to being
  99.         // entirely in ascending order, the fewer times step 3 will need to
  100.         // enumerate the history array the next time this method is called.
  101.         // In essence: Whenever a pair of elements are encountered that are
  102.         // relatively in descending order, they are swapped to instead be
  103.         // in ascending order.
  104.         // TODO: At what point, if any, does this stop being more performant
  105.         // than a Fisher-Yates shuffle? Can a heuristic be used to choose
  106.         // which algorithm to use depending on the length of the input?
  107.         if(this.valueHistory.length < this.totalValues){
  108.             let i = Math.floor(this.random() * (
  109.                 this.totalValues - this.valueHistory.length
  110.             ));
  111.             let j = i;
  112.             for(const value of this.valueHistory){
  113.                 if(value <= i) i++;
  114.             }
  115.             while(i !== j){
  116.                 j = i;
  117.                 if(this.valueHistory[0] >= j && this.valueHistory[0] <= i) i++;
  118.                 for(let k = 1; k < this.valueHistory.length; k++){
  119.                     const value = this.valueHistory[k];
  120.                     // Increment i where appropriate
  121.                     if(value >= j && value <= i) i++;
  122.                     // Ensure that values appear in ascending order
  123.                     if(value < this.valueHistory[k - 1]){
  124.                         this.valueHistory[k] = this.valueHistory[k - 1];
  125.                         this.valueHistory[k - 1] = value;
  126.                     }
  127.                 }
  128.             }
  129.             console.log(
  130.                 "Unpicked values:", hi.range(this.start,this.end).filter(
  131.                     n=>!hi.any(this.valueHistory, x=>x===n)
  132.                 ).array()
  133.             );
  134.             console.log("History:", this.valueHistory);
  135.             console.log(
  136.                 "Picked value no.", i, " of ", this.left(), ":", j
  137.             );
  138.             this.valueHistory.push(j);
  139.         }else{
  140.             this.valueHistory.push(undefined);
  141.         }
  142.     },
  143.     back: null,
  144.     popBack: null,
  145.     index: null,
  146.     slice: null,
  147.     has: null,
  148.     get: null,
  149.     // Can't copy or reset since result is nondeterministic.
  150.     // TODO: Use resettable RNG objects instead of e.g. Math.random?
  151.     copy: null,
  152.     reset: null,
  153. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement