Reputation: 343
I'm trying to understand the concept of recursion and wanted to use it in my code (the getUniqueInt function):
var getRandomInt = function (min, max) {
return Math.floor(Math.random() * (max - min)) + min;
};
var getChosenNumbers = function (min, max) {
var chosenNumbers = [];
for (var k = min; k <= max; k++) {
chosenNumbers.push(k);
}
return chosenNumbers;
};
var arrayOfNumbers = getChosenNumbers(1, 8);
var getUniqueInt = function (min, max) {
var uniqueNumber;
var randomNumber = getRandomInt(min, max);
if (arrayOfNumbers.indexOf(randomNumber) !== -1) {
uniqueNumber = randomNumber;
arrayOfNumbers.splice(arrayOfNumbers.indexOf(uniqueNumber), 1);
} else {
uniqueNumber = getUniqueInt(min, max);
}
return uniqueNumber;
};
But I end up getting this: Uncaught RangeError: Maximum call stack size exceeded
What am I doing wrong? And does my code (I mean the recursion part) make any sense or it's totally wrong?
Upvotes: 0
Views: 1860
Reputation: 135207
But what I am basically trying to do here is to get a range of numbers (array of numbers) and then choose one of these numbers randomly without repeating the already chosen ones;
Your getRandomInt
function is a good start -
const getRandomInt = (min = 0, max = 0) =>
Math.floor(Math.random() * (max - min)) + min
Let's make a function to make a range of numbers -
const makeRange = (min = 0, max = 0) =>
min > max
? []
: [ min, ...makeRange(min + 1, max) ]
We don't have to loop to find the next random value. We can swap
elements in an array to efficiently create a random sequence. This technique is known as Fisher-Yates shuffle -
const getUniqueRandom = (min = 0, max = 0) =>
{ const r = makeRange(min, max)
const next = (i = 0) =>
{ if (i >= r.length) return undefined
swap(r, i, getRandomInt(i, r.length))
return r[i]
}
let i = 0
const rand = () =>
next(i++)
return rand
}
Finally we need to write the swap
function -
const swap = (a = [], i = 0, j = 0) =>
[a[j], a[i]] = [a[i], a[j]]
Now here's how it works -
const rand = getUniqueRandom(3,7)
console.log(rand()) // 4
console.log(rand()) // 7
Keep calling it to get the rest of the values. It returns undefined
when there is no possible unique to output -
console.log(rand()) // 3
console.log(rand()) // 6
console.log(rand()) // 5
console.log(rand()) // undefined
Expand the snippet below to verify the output in your own browser. Press Run many times to see the randomised outputs -
const getRandomInt = (min = 0, max = 0) =>
Math.floor(Math.random() * (max - min)) + min
const makeRange = (min = 0, max = 0) =>
min > max
? []
: [ min, ...makeRange(min + 1, max) ]
const swap = (a = [], i = 0, j = 0) =>
[a[j], a[i]] = [a[i], a[j]]
const getUniqueRandom = (min, max) =>
{ const r = makeRange(min, max)
const next = (i = 0) =>
{ if (i >= r.length)
return undefined
swap(r, i, getRandomInt(i, r.length))
return r[i]
}
let i = 0
const rand = () =>
next(i++)
return rand
}
const rand = getUniqueRandom(3,7)
console.log(rand()) // 4
console.log(rand()) // 7
console.log(rand()) // 3
console.log(rand()) // 6
console.log(rand()) // 5
console.log(rand()) // undefined
generators
What rand
demonstrates above is some sort of generator. Modern JavaScript has native support for Generators allowing us to write this program in a convenient way. You may have heard them called coroutines in other languages.
Here's a very simple generator we can use for makeRange
. Note the use of yield
instead of return
-
const makeRange = function* (min = 0, max = 0)
{ while (min <= max)
yield min++
}
And here's a rewrite of getUniqueRandom
. We can gather all the values from makeRange(...)
by using Array.from
-
const getUniqueRandom = function* (min, max)
{ const r =
Array.from(makeRange(min, max))
for (let i = 0; i < r.length; i++)
{ swap(r, i, getRandomInt(i, r.length))
yield r[i]
}
}
To get the unique randoms out one-by-one -
const rand = getUniqueRandom(3,7)
console.log(rand.next()) // { value: 7, done: false }
console.log(rand.next()) // { value: 3, done: false }
Just as before, keep calling to get the next unique randoms. We see value: undefined
and done: true
when there are no more results -
console.log(rand.next()) // { value: 6, done: false }
console.log(rand.next()) // { value: 5, done: false }
console.log(rand.next()) // { value: 4, done: false }
console.log(rand.next()) // { value: undefined, done: true }
Just like we did with makeRange
, if we want all of the results right away, we can simply use Array.from
-
console.log(Array.from(getUniqueRandom(3, 7)))
// [ 6, 3, 4, 5, 7 ]
Expand the snippet below to verify the output in your own browser. Press Run many times to see the randomised outputs -
const getRandomInt = (min = 0, max = 0) =>
Math.floor(Math.random() * (max - min)) + min
const swap = (a = [], i = 0, j = 0) =>
[a[j], a[i]] = [a[i], a[j]]
const makeRange = function* (min = 0, max = 0)
{ while (min <= max)
yield min++
}
const getUniqueRandom = function* (min, max)
{ const r =
Array.from(makeRange(min, max))
for (let i = 0; i < r.length; i++)
{ swap(r, i, getRandomInt(i, r.length))
yield r[i]
}
}
const rand = getUniqueRandom(3,7)
console.log(rand.next()) // { value: 7, done: false }
console.log(rand.next()) // { value: 3, done: false }
console.log(rand.next()) // { value: 6, done: false }
console.log(rand.next()) // { value: 5, done: false }
console.log(rand.next()) // { value: 4, done: false }
console.log(rand.next()) // { value: undefined, done: true }
console.log(Array.from(getUniqueRandom(3, 7)))
// [ 6, 3, 4, 5, 7 ]
Upvotes: 1
Reputation: 11930
Your code doesn't make sense, sorry, and here's why
set min=0, max=10
getRandomInt
returns random int within 0-10
getChosenNumbers
returns array of int FROM 0-10, meaning [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arrayOfNumbers
is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
now
getUniqueInt
simply cannot get uniq, because all possible random values of getRandomInt
are already within getChosenNumbers
Thats why
else {
uniqueNumber = getUniqueInt(min, max);
}
gets called infinite times, because
arrayOfNumbers.indexOf(randomNumber) !== -1
is never true
Upvotes: 1