1

I am trying to add some code where the user clicks a button and the code generates 3 numbers (none repeating, so 245 is OK but 122 and 121 is not) and display them onscreen, one each second. To ensure there are no repeats, I am using an array: var usedNums = [];. Then, I create the number (digit = Math.random()) and check if it is already in the array, and if it's not, add it, like this:

if ($.inArray(digit, usedNums) !== -1) {
        newNums();
    } else {
        usedNums.push(digit);
        $('#memDigit').html(digit);

}

The first few times, it works, but when I click it for the 10th time, I get the Uncaught RangeError: Maximum call stack size exceeded error. Help!

Here's the full code:

var usedNums = [];
var digit;
var amount = 3;

function newNums() {
    digit = Math.floor(Math.random() * 10);
    if ($.inArray(digit, usedNums) !== -1) {
        newNums();
    } else {
        usedNums.push(digit);
        $('#memDigit').html(digit);

    }

}

function createNums() {
    for (var i; i < amount; i++) {
        setTimeout(newNums, 1000);
    }
}

//$(document).ready(createNums);
MrConorAE
  • 23
  • 1
  • 8

2 Answers2

0

I think you should empty/reinitialize the array usedNums after each number generation is complete.

Wadih M.
  • 12,810
  • 7
  • 47
  • 57
0

If IE <= 10 is not a concern, you may want to take advantage of crypto.getRandomValues to do this in one shot instead.

The getRandomValues method fills a TypedArray with a series of random values in the range supported by that array type. If we ask for 10 of these vales, we now have, in effect, a hash of the numbers 0-9 (that is, the indices of the array) pre-associated with random values for sorting by (the values). The result will be both more random and — unlike the ‘already seen this value, try again’ pattern — it should run in constant time.

ES6:

const nums = [ ...crypto.getRandomValues(new Int16Array(10)) ]
  .map((sortVal, num) => [ sortVal, num ])
  .sort(([ a ], [ b ]) => a < b ? -1 : Number(a > b))
  .map(([ , num ]) => num)
  .slice(0, 3);

ES5:

var nums = [].slice.call(crypto.getRandomValues(new Int16Array(10)))
  .map(function(sortVal, num) { return [ sortVal, num ]; })
  .sort(function(a, b) { return a[0] < b[0] ? -1 : Number(a[0] > b[0]); })
  .map(function(pair) { return pair[1]; })
  .slice(0, 3);

Breakdown:

  • new Int16Array(10) creates an empty TypedArray of length 10
  • crypto.getRandomValues(...) fills that array with random values
  • We convert the result to a regular array
  • We map that to [ randomVal, index ] tuples
  • We sort that by the random value (remembering to coerce to Number explicitly, to cover the Safari sort implementation bug)
  • We map down to just the index — i.e., a number from 0-9
  • We grab just the first three values
Semicolon
  • 6,793
  • 2
  • 30
  • 38
  • Good idea, but why not use `Math.random()`? – MrConorAE Dec 18 '16 at 19:57
  • Mainly because using `Math.random`, at least as it was used in the original example, means you have to fetch an unknown number of values until you find one that satisfies the constraints. Admittedly, the chance of getting the same number repeatedly for a long enough time to impact the user is exceedingly low, but it seems kind of hacky regardless when the DOM already offers an API that lets you do this in constant time. Another potential reason to prefer `getRandomValues` — it’s unclear whether this is significant here — is that it is allegedly cryptographically secure, unlike `Math.random`. – Semicolon Dec 19 '16 at 00:58