3

I use the following code to get random value from specifed range

var provideRanges = function(){
    var aa = [];
    _.times(3, function(i) {
      console.log(i);
      let num = _.random(10, 20)
      console.log("num = :" + num)
      debugger;
      aa.push(num);
      console.log("value is :" + aa[i]);
    });
}

This is working and provide array of 3 values between the specified range when you call to this function, but the problem here become more difficult when I need that if I call it again ignore exclude the previous provided number from the provided numbers (like if it provide 10,11,12 the next time those numbers should not be provided...), is there is nicer way to do it? I try to use recoursive call but get lost:( ,any idea how to do it?

John Jerrby
  • 1,683
  • 7
  • 31
  • 68
  • try this , its java but the core idea is there, might help you with the pseudo code: http://stackoverflow.com/questions/6443176/how-can-i-generate-a-random-number-within-a-range-but-exclude-some/6443346 – rule Dec 06 '16 at 17:15
  • What is expected result for third call to function? Should the returned numbers not be repeated at any subsequent call? – guest271314 Dec 06 '16 at 17:17
  • @guest271314 - yes exclude all the previous provided value... – John Jerrby Dec 06 '16 at 17:19
  • @JohnJerrby Have not tried `lodash`, not familiar with `_.random()` function, though should be possible; see [Random integer in a certain range excluding one number](http://stackoverflow.com/questions/34182699/random-integer-in-a-certain-range-excluding-one-number), [Random number, which is not equal to the previous number](http://stackoverflow.com/questions/40056297/random-number-which-is-not-equal-to-the-previous-number/). What should be returned when all numbers in range have been previously returned? – guest271314 Dec 06 '16 at 17:21
  • splice out the returned random numbers from your range array each time you make a call to that function so that the next time you won't meet them. – Redu Dec 06 '16 at 18:05
  • @guest271314 Why did you reopen this question? – Michał Perłakowski Dec 06 '16 at 18:27
  • 2
    @Gothdo _"Why did you reopen this question?"_ Yes, voted for present Question to be reopened. As interpreted present Question here, the linked Question does not ask for same result as current Question. Linked question asks for single set of unique values to be returned from original set. Present Question asks for same function to be called in succession, returning more than one set of unique values from original set. It is not clear if the function should be called until no unique options remain to be returned from original input set, though function is expected to be called more than once. – guest271314 Dec 06 '16 at 18:55
  • @guest271314 If it's not clear what the question asks for, you should comment with an inquiry, not vote to reopen or answer. – Bergi Dec 06 '16 at 20:44
  • 2
    @Bergi _"If it's not clear what the question asks for, you should comment with an inquiry, not vote to reopen or answer."_ With all due respect, neither you, nor any other user can advise this user, nor in actuality should attempt to persuade another user, how the individual "should vote". That is each users' prerogative and should not be influenced by any other user; the decision should be guided by the users' own perspective and judgement. The only portion of the Question which is not clear is what should occur when less than three unique values remain in original set. – guest271314 Dec 06 '16 at 20:50
  • @guest271314 The [privilege to vote](http://stackoverflow.com/help/privileges/close-questions) comes with guidelines on when to vote and when not. If you don't follow them to an extent that causes harm, this can even get you banned. Feel free to ask at meta. – Bergi Dec 06 '16 at 21:01
  • 2
    @Bergi Do you really want to commence this type of exchange? Is this the proper venue? Meta is not the end-all, be-all and can express its peer-pressure influences quite vividly; even in the face of facts. No two-legged will influence this users' vote. Am capable of making own decisions. You commented to OP at linked Question _"Frankly told, I don't really understand your approach."_ http://stackoverflow.com/questions/11808804/generate-unique-number-within-range-0-x-keeping-a-history-to-prevent-duplic#comment15695235_11809348 at your own Answer. Did you vote to close the linked Question? – guest271314 Dec 06 '16 at 21:28

4 Answers4

4

One approach would be to create an array to store existing selections, push selected elements to an array, check if the array contains the element and if the array storing values .length is greater than or equal to maximum range minus minimum range.

It is not clear from description at Question what should occur once all of the elements in the range have been returned?

var range = [10, 20];
var not = [];

function randomRange(range, n) {
  if (not.length >= range[1] - range[0]) {
    return "all numbers in range used"
  }
  var curr = [];
  var res = [];
  for (let i = range[0]; i < range[1]; i++) {
    if (!not.some(function(num) {
      return i == num
    }) && not.length < range[1] - range[0]) {
      curr.push(i)
    }
  }
  for (let i = 0; i < n; i++) {
    var j = curr.splice(Math.floor(Math.random() * curr.length), 1)[0];
    res[i] = not[not.length] = j;
  }

  return res.filter(Boolean)
}

console.log(randomRange(range, 3));
console.log(randomRange(range, 3));
console.log(randomRange(range, 3));
console.log(randomRange(range, 3));
console.log(randomRange(range, 3));
guest271314
  • 1
  • 15
  • 104
  • 177
  • Thanks you very much! I'll try it and let know – John Jerrby Dec 06 '16 at 18:48
  • In case that all the values was returend it should run again from the beginning , you code is just stoping... – John Jerrby Dec 06 '16 at 18:50
  • @JohnJerrby The original Question did not appear to indicate what should occur when all of the values have been returned. You can include `not.length = 0` at first `if` statement `if (not.length >= range[1] - range[0]) { not.length = 0; // or not = [] }` to continue process. – guest271314 Dec 06 '16 at 19:00
  • @JohnJerrby plnkr http://plnkr.co/edit/cyJWtTC0VQpsvEqvmT17?p=preview – guest271314 Dec 06 '16 at 19:04
  • @JohnJerrby If you are expecting an array having three elements to be returned at each call to the function you can create a range set divisible by `3`, or an array having values of range that has a `.length` divisible by `3`. The function should then always return an array having `.length` of `3`. Note also, the original Question does not describe that the returned array should always have a `.length` of `3`, where the range at Question, `10`, is not evenly divisible by `3`. See http://stackoverflow.com/help/how-to-ask – guest271314 Dec 06 '16 at 19:16
2

My solution uses Set to retain the uniqueness of the values:

const getRangeRandomizer = ([min, max]) => { // create the range randomizer
  const state = new Set(); // the numbers that were produced from the range
  
  /** the returned method can be used to get more numbers from the range **/
  return (numOfRandoms) => {
    const discarded = new Set(); // this Set is used to count numbers that are not unique
    const range = max - min + 1;
    const result = [];
    let i = 0;
    let randNum;

    while (i < numOfRandoms && discarded.size < range) { // if we added numOfRandoms times or we discarded delta optionsm the loop end - this prevents endless loops
      randNum = Math.floor(Math.random() * (range) + min);

      if (state.has(randNum)) {
        discarded.add(randNum);
      } else {
        state.add(randNum);
        result.push(randNum);
        i++;
      }
    }

    return result;
  }
}

const provideRanges = getRangeRandomizer([10, 20]); // create a range randomizer

console.log(provideRanges(4));
console.log(provideRanges(3));
console.log(provideRanges(2));
console.log(provideRanges(4)); // only 2 results will be provided, because the rest were used

const provideOtherRanges = getRangeRandomizer([15, 20]);  // create a range randomizer
console.log(provideOtherRanges(100));
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • Duplicates are returned at subsequent calls to `provideRanges`? – guest271314 Dec 06 '16 at 19:34
  • Requirement appears to be call same function in succession to return array having unique values from original set, without duplicating values within returned arrays; at least until all values within range have been returned. https://jsfiddle.net/tfmrasr6/ – guest271314 Dec 06 '16 at 19:41
  • One value from range is omitted from being returned where `provideRanges.clearAdded()` is called following third call to `provideRanges()`? Granted, the range described at OP `10` is not evenly divisible by `3`. Not clear what expected result is for that case. – guest271314 Dec 06 '16 at 20:03
  • If `console.log(provideRanges([10, 20], 3))` is called three times in succession only nine of the ten possible range options is returned, not all ten possible range values. – guest271314 Dec 06 '16 at 20:35
  • 1
    Isn't it 11 values (10-20 including both)? – Ori Drori Dec 06 '16 at 20:36
  • Yes, you are correct; good catch. `11` is still not evenly divisible by `3`. Original Question does not describe what should occur in the case of last values which are not exactly three; though at comments OP suggests that process should begin again. – guest271314 Dec 06 '16 at 20:39
  • Indeed. I've updated my examples accordingly. That was an interesting question. Thanks for reopening. – Ori Drori Dec 06 '16 at 20:42
  • There was already an existing reopen vote at that time, though that fact did not influence reopen vote, here. Previously posted reason therefor, fwiw, when asked, above. From perspective here, text of Question itself is still not clear as to expected result when less than three unique values remain in original set. – guest271314 Dec 06 '16 at 20:44
  • It was my reopen vote. I agree that it's not clear, but my view is that the amount of results will be less then expected in the end, and it can also be an empty array. – Ori Drori Dec 06 '16 at 20:50
1

Here's a solution using lodash's sampleSize and pullAt:

// numbers = [10, 11, ... 19]
let numbers = _.range(10, 20);

// sampleSize will return an array containg the numbers randomized
let sample = _.sampleSize(numbers, numbers.length)

// get the first 3 numbers from the sample (pullAt will mutate sample)
let first3 = _.pullAt(sample, [0,1,2]);

// get the next 3 numbers etc.
let next3 = _.pullAt(sample, [0,1,2]);

let numbers = _.range(10, 20);

let sample = _.sampleSize(numbers, numbers.length)

let sample1 = _.pullAt(sample, [0,1,2]);
let sample2 = _.pullAt(sample, [0,1,2]);
let sample3 = _.pullAt(sample, [0,1,2]);
let sample4 = _.pullAt(sample, [0,1,2]);


console.log(sample1)
console.log(sample2)
console.log(sample3)
console.log(sample4)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
Gruff Bunny
  • 27,738
  • 10
  • 72
  • 59
  • when I try your solution like following the third entry get undfiend,any idea ? let next1 = _.pullAt(sample, [0,1,2]); let next2 = _.pullAt(sample, [0,1,2]); let next3 = _.pullAt(sample, [0,1,2]); console.log(next1); console.log(next2); console.log(next3); – John Jerrby Dec 06 '16 at 19:01
  • I've added a code snippet showing it working. You will get undefined in the arrays when all the numbers in the sample have been used e.g if you have 10 numbers in the sample array then the 4th call to `_.pullAt` will return something like `[12, undefined, undefined]` – Gruff Bunny Dec 06 '16 at 19:52
  • Thanks but in case the all the values was provided it should run again and not provide the undefined .... – John Jerrby Dec 06 '16 at 20:00
0

You may do as follows

function RandomSomething(n,m){
  this.choices = new Array(n).fill().map((_,i) => i+m);
}

RandomSomething.prototype.randomFromRange = function(){
                                              return new Array(3).fill()
                                                                 .map(function(r,i){
                                                                        r = this.choices.length > 3 ? this.choices
                                                                                                          .splice(Math.floor((Math.random()*this.choices.length)),1)[0]
                                                                                                    : this.choices[i];
                                                                        return r;
                                                                      }.bind(this));
                                            };

var obj = new RandomSomething(10,100); // generate randoms at range 100-109
console.log(JSON.stringify(obj.randomFromRange()));
console.log(JSON.stringify(obj.randomFromRange()));
console.log(JSON.stringify(obj.randomFromRange()));
console.log(JSON.stringify(obj.randomFromRange()));
console.log(JSON.stringify(obj.randomFromRange()));
Redu
  • 25,060
  • 6
  • 56
  • 76