Is this implementation of an in-place shuffle "uniform?" [duplicate] - javascript

This question already has answers here:
What distribution do you get from this broken random shuffle?
(10 answers)
Closed 5 years ago.
Sorry if this question is oddly specific; I wasn't sure where else to ask it, though!
I'm working through some problems, and I've found the Fisher-Yates Shuffle, but I want to know if this first shuffle I thought of is uniform or not? I'm not the greatest with probability.. or math... Ha.
The idea is pretty simple; for as many elements as there are in the array, pick two at random and swap them with each other.
function inPlaceShuffle(arr) {
const floor = 0,
ceil = arr.length - 1;
let i = arr.length;
while (i > 0) {
swap(arr, getRandom(floor, ceil), getRandom(floor, ceil));
i--;
}
return arr;
}
function swap(arr, firstIndex, secondIndex) {
const temp = arr[firstIndex];
arr[firstIndex] = arr[secondIndex];
arr[secondIndex] = temp;
}
function getRandom(floor, ceil) {
floor = Math.ceil(floor);
ceil = Math.floor(ceil);
return Math.floor(Math.random() * (ceil - floor + 1)) + floor;
}

Note that every iteration of loop gives n^2 combinations of indexes for change, and n iterations give n^(2n) variants, but this value does not divide n! permutations evenly for n>2, so some permutations will have higher probability than others.
Clear example:
There are 729 results for 6 permutations of {1,2,3}, 729 / 6 = 121. 5, so probability of permutations varies.

Related

Generate random & unique 4 digit codes without brute force

I'm building an app and in one of my functions I need to generate random & unique 4 digit codes. Obviously there is a finite range from 0000 to 9999 but each day the entire list will be wiped and each day I will not need more than the available amount of codes which means it's possible to have unique codes for each day. Realistically I will probably only need a few hundred codes a day.
The way I've coded it for now is the simple brute force way which would be to generate a random 4 digit number, check if the number exists in an array and if it does, generate another number while if it doesn't, return the generated number.
Since it's 4 digits, the runtime isn't anything too crazy and I'm mostly generating a few hundred codes a day so there won't be some scenario where I've generated 9999 codes and I keep randomly generating numbers to find the last remaining one.
It would also be fine to have letters in there as well instead of just numbers if it would make the problem easier.
Other than my brute force method, what would be a more efficient way of doing this?
Thank you!
Since you have a constrained number of values that will easily fit in memory, the simplest way I know of is to create a list of the possible values and select one randomly, then remove it from the list so it can't be selected again. This will never have a collision with a previously used number:
function initValues(numValues) {
const values = new Array(numValues);
// fill the array with each value
for (let i = 0; i < values.length; i++) {
values[i] = i;
}
return values;
}
function getValue(array) {
if (!array.length) {
throw new Error("array is empty, no more random values");
}
const i = Math.floor(Math.random() * array.length);
const returnVal = array[i];
array.splice(i, 1);
return returnVal;
}
// sample code to use it
const rands = initValues(10000);
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
This works by doing the following:
Generate an array of all possible values.
When you need a value, select one from the array with a random index.
After selecting the value, remove it from the array.
Return the selected value.
Items are never repeated because they are removed from the array when used.
There are no collisions with used values because you're always just selecting a random value from the remaining unused values.
This relies on the fact that an array of integers is pretty well optimized in Javascript so doing a .splice() on a 10,000 element array is still pretty fast (as it can probably just be memmove instructions).
FYI, this could be made more memory efficient by using a typed array since your numbers can be represented in 16-bit values (instead of the default 64 bits for doubles). But, you'd have to implement your own version of .splice() and keep track of the length yourself since typed arrays don't have these capabilities built in.
For even larger problems like this where memory usage becomes a problem, I've used a BitArray to keep track of previous usage of values.
Here's a class implementation of the same functionality:
class Randoms {
constructor(numValues) {
this.values = new Array(numValues);
for (let i = 0; i < this.values.length; i++) {
this.values[i] = i;
}
}
getRandomValue() {
if (!this.values.length) {
throw new Error("no more random values");
}
const i = Math.floor(Math.random() * this.values.length);
const returnVal = this.values[i];
this.values.splice(i, 1);
return returnVal;
}
}
const rands = new Randoms(10000);
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
Knuth's multiplicative method looks to work pretty well: it'll map numbers 0 to 9999 to a random-looking other number 0 to 9999, with no overlap:
const hash = i => i*2654435761 % (10000);
const s = new Set();
for (let i = 0; i < 10000; i++) {
const n = hash(i);
if (s.has(n)) { console.log(i, n); break; }
s.add(n);
}
To implement it, simply keep track of an index that gets incremented each time a new one is generated:
const hash = i => i*2654435761 % (10000);
let i = 1;
console.log(
hash(i++),
hash(i++),
hash(i++),
hash(i++),
hash(i++),
);
These results aren't actually random, but they probably do the job well enough for most purposes.
Disclaimer:
This is copy-paste from my answer to another question here. The code was in turn ported from yet another question here.
Utilities:
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q]
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N
}
Usage
// Each day you bootstrap to get a tuple of these parameters and persist them throughout the day.
const [firstIndex, N, Q] = getIndexGeneratorParams(10000)
// need to keep track of previous index generated.
// it’s a seed to generate next one.
let prevIndex = firstIndex
// calling this function gives you the unique code
function getHashCode() {
prevIndex = getNextIndex(prevIndex, N, Q)
return prevIndex.toString().padStart(4, "0")
}
console.log(getHashCode());
Explanation
For simplicity let’s say you want generate non-repeat numbers from 0 to 35 in random order. We get pseudo-randomness by polling a "full cycle iterator"†. The idea is simple:
have the indexes 0..35 layout in a circle, denote upperbound as N=36
decide a step size, denoted as Q (Q=23 in this case) given by this formula‡
Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
randomly decide a starting point, e.g. number 5
start generating seemingly random nextIndex from prevIndex, by
nextIndex = (prevIndex + Q) % N
So if we put 5 in we get (5 + 23) % 36 == 28. Put 28 in we get (28 + 23) % 36 == 15.
This process will go through every number in circle (jump back and forth among points on the circle), it will pick each number only once, without repeating. When we get back to our starting point 5, we know we've reach the end.
†: I'm not sure about this term, just quoting from this answer
‡: This formula only gives a nice step size that will make things look more "random", the only requirement for Q is it must be coprime to N
This problem is so small I think a simple solution is best. Build an ordered array of the 10k possible values & permute it at the start of each day. Give the k'th value to the k'th request that day.
It avoids the possible problem with your solution of having multiple collisions.

Array of random numbers optimization

I have a function that generates an array of random numbers. It works, but I feel that it might works slow on big numbers. Is there a way how to optimize it?
function renerateRandomNumbers(maxNumber, randomNumbersCount) {
let i;
const arrResult = [];
for (i = 0; i < randomNumbersCount; i++) {
let rand = Math.random() * (maxNumber);
rand = Math.round(rand);
if (arrResult.indexOf(rand) === -1 ) {
arrResult.push(rand);
} else {
i--;
}
}
return arrResult;
}
EDIT - To any future users, #ScottSauyet's solution should be the accepted answer. It is a more consistently efficient solution than mine.
I think the most algorithmically efficient way to solve this would be to generate the list of all possible numbers from 0-maxNumber, shuffle that array (O(n)), and then take the first randomNumbersCount numbers from the shuffled array. It would look like the following:
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function generateRandomNumbers(maxNumber, randomNumbersCount) {
var possibleNumbers = [];
// populate array with all possible values
for (var i = 0; i <= maxNumber; i++) { possibleNumbers.push(i); }
// shuffle the array to get a random order of the possible numbers O(n)
shuffleArray(possibleNumbers);
// trim the array down to only the first n numbers where n = randomNumbersCount
possibleNumbers.length = randomNumbersCount;
return possibleNumbers;
}
console.log (generateRandomNumbers(10, 5));
console.log (generateRandomNumbers(10, 5));
console.log (generateRandomNumbers(10, 5));
The problem of your code is that complexity grows geometrically because it have a chance generate number that was already picked multiple times.
What we need to achieve is to get number on every iteration to achieve iterations count to be equal to the randomNumbersCount.
How to avoid multiple same random numbers?
let's say you want to have 5 random numbers from 0-10 range
First iteration
Create an array with values var candidates = [0,1...10]
Generate random number let's say 0
Store the number candidates[0] in results
Remove 0 from candidates. To avaoid reindexing of the candidates array we will put candidates[candidates.length - 1] into candidates[0] and remove candidates[candidates.length - 1]
and then will do this operation randomNumbersCount times.
Second iteration
Our candidates array is now [10,1,2,3,4,5,6,7,8,9]
Generate random number let's say 0 again. Wow we generated similar random number, but so what?
we alreay have 0 in our results, but candidates[0] is not a 0 anymore candidates[0] is 10 right now
so we pick candidates[0] that is 10 and will store it and remove it from candidates. Put candidates[candidates.length - 1] (9) into candidates[0] and remove candidates[candidates.length - 1]
our result is [0, 10] right now
Third iteration
Our candidates is now [9,1,2,3,4,5,6,7,8]
Generate random number let's say 0
we are not worring anymore because we know that candidates[0] is 9
add candidates[0] (witch is 9) we are saving to results, and remove it from candidates
our result is [0,10,9], candidates is [8,1,2,3,4,5,6,7]
And so on
BTW implementation is much shorter than explanation:
function renerateRandomNumbers(maxNumber, randomNumbersCount) {
var candidates = [...Array(maxNumber).keys()];
return Array(randomNumbersCount).fill()
.map(() => {
const randomIndex = Math.floor(Math.random() * candidates.length)
const n = candidates[randomIndex]
candidates[randomIndex] = candidates[candidates.length - 1]
candidates.length = candidates.length - 1
return n
})
.sort((a, b) => a - b) // sort if needed
}
console.log (renerateRandomNumbers(10, 5))
The solution from mhodges is reasonably efficient, but only when the sought count is fairly close to the max number. If your count is significantly smaller, this can be a problem, as the solution is O(m + n) where m is the maximum and n is the desired count. It's also O(m) in space. If m is large, this could be a problem.
A variant would make this approximately O(n) in time and space, by doing the same thing, but stopping the shuffle when when we've reached count items and by not pre-filling the array but instead defaulting to its indices.
function venerateRandomNumbers(max, count) {
// todo: error if count > max
const arr = new Array(max + 1)
for (let i = max; i > max - count; i--) {
const j = Math.floor(Math.random() * (i + 1))
const temp = arr[j] || j
arr[j] = arr[i] || i
arr[i] = temp
}
return arr.slice(-count)
}
console.log(venerateRandomNumbers(1000000, 10))
You can see performance comparisons on repl.it

Get random number based on probability [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I was wondering to get a random number with two decimal places based on probability for example:
40% to get number from 1-10
20% to get number from 11-20
30% to get number from 21-30
10% to get number from 31-35
function Prob(){
var rnd = Math.random(),
rnd2 = Math.random();
if(rnd<0.4) return (1 + Math.floor(1000 * rnd2)/100);
else if(rnd<0.6) return (11 + Math.floor(1000 * rnd2)/100);
else if(rnd<0.9) return (21 + Math.floor(1000 * rnd2)/100);
else return (31 + Math.floor(500 * rnd2)/100);
}
You need two random numbers, so I calculate them at the start. I then use the if-else loops to cycle through your 40%, 20%, 30% and 10% (adding them up as I go). Note: Math.random returns a number between 0 and 1. Then for each catagory I use the SECOND random number to get in the range you have said - floor it to make sure it is an integer and add the starting number of each range. Note: the range of your last one is just 5.
I should explain, you must use two random numbers, otherwise the range of the second number would be dependent on which category you are in.
I have to do the 1000 * rnd2 in the floor and then divide by 100 outside to get the 2 decimal place you ask for.
Rewind's solution is great and specifically tailored to OP's quesiton. A more re-usable solution might be:
function getNumber(probabilities){
var rnd = Math.random();
var total = 0;
var hit;
for(var i = 0; i < probabilities.length; i++){
if(rnd > total && rnd < total + probabilities[i][0]){
hit = probabilities[i]
}
total += probabilities[i][0];
}
return Number((hit[1] + (Math.random() * (hit[2] - hit[1]))).toFixed(2));
}
var number = getNumber(
[
//chance, min, max
[0.4, 1, 10],
[0.2,11,20],
[0.3,21,30],
[0.1,31,35]
]
);
console.log(number);
The function will take an array with the probabilities, for each probability you specify the chance, the minimum value for that chance, the maximum value for that chance. It will return a number with two decimals.
https://jsfiddle.net/x237w5gv/
I guess this
var get1120 = _ => ~~(Math.random()*10)+11,
get2130 = _ => ~~(Math.random()*10)+21,
get3135 = _ => ~~(Math.random()*5)+31,
a = [get3135,get1120,get1120,get2130,get2130,get2130],
fun;
result = (fun = a[~~(Math.random()*10)]) ? fun() : ~~(Math.random()*10)+1;
console.log(result);
might do it;

Two random numbers [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Generate random between 0 - 9 but not 6
var ifanse=Math.floor(Math.random()*4)+1;
var ifanse2=Math.floor(Math.random()*3)+1
These are 2 random numbers. Sometimes they come equal, but is there any way if ifanse come equal to ifanse2, it will regenerate a random between 3 - 1 but exclusive of ifanse2. Or is there any way to avoid equaling at first place?
You could loop until you pick a different number:
var ifanse=Math.floor(Math.random()*4)+1;
var ifanse2=Math.floor(Math.random()*3)+1;
while (ifanse2 == ifanse)
{
ifanse2=Math.floor(Math.random()*3)+1;
}
You could for example write a generic function which gives you an array of random numbers
Filling and Array with the Numbers of your range Which will eliminate duplicate numbers
shuffling the Array
return an Array of the lenght, with the count of random Numbers you want
function genRand(min, max, cnt) {
var arr = [];
for (var i = min, j = 0; i <= max; j++, i++)
arr[j] = i
arr.sort(function () {
return Math.floor((Math.random() * 3) - 1)
});
return arr.splice(0, cnt)
}
console.log(genRand(0, 3, 2)) // e.g [0,3]
Then you could just store them in your var, or access them directly from rands
var rands = genRand(0,3,2);
var ifanse = rands[0]
var ifanse2 = rands[1]
You would never get 2 equal Numbers with this and you can generate more then 2 different rands if you ever need to.
Heres a Jsbin

javascript to generate 50 no-repeat random numbers [duplicate]

This question already has answers here:
How to generate random numbers with no repeat javascript
(4 answers)
Closed 3 years ago.
I want to use javascript to generate 50 no-repeat random numbers and the number is in the range 1 to 50.how can I realize it?The 50 numbers stored in a array.
First generate an ordered list:
var i, arr = [];
for (i = 0; i < 50; i++) {
arr[i] = i + 1;
}
then shuffle it.
arr.sort(function () {
return Math.random() - 0.5;
});
I tested the above method and it performed well. However, the ECMAScript spec does not require Array.sort to be implemented in such a way that this method produces a truly randomized list - so while it might work now, the results could potentially change without warning. Below is an implementation of the Fisher-Yates shuffle, which is not only guaranteed to produce a reasonably random distribution, but is faster than a hijacked sort.
function shuffle(array) {
var p, n, tmp;
for (p = array.length; p;) {
n = Math.random() * p-- | 0;
tmp = array[n];
array[n] = array[p];
array[p] = tmp;
}
}

Categories

Resources