I'm trying to devise a (good) way to choose a random number from a range of possible numbers where each number in the range is given a weight. To put it simply: given the range of numbers (0,1,2) choose a number where 0 has an 80% probability of being selected, 1 has a 10% chance and 2 has a 10% chance.
It's been about 8 years since my college stats class, so you can imagine the proper formula for this escapes me at the moment.
Here's the 'cheap and dirty' method that I came up with. This solution uses ColdFusion. Yours may use whatever language you'd like. I'm a programmer, I think I can handle porting it. Ultimately my solution needs to be in Groovy - I wrote this one in ColdFusion because it's easy to quickly write/test in CF.
public function weightedRandom( Struct options ) {
var tempArr = [];
for( var o in arguments.options )
{
var weight = arguments.options[ o ] * 10;
for ( var i = 1; i<= weight; i++ )
{
arrayAppend( tempArr, o );
}
}
return tempArr[ randRange( 1, arrayLen( tempArr ) ) ];
}
// test it
opts = { 0=.8, 1=.1, 2=.1 };
for( x = 1; x<=10; x++ )
{
writeDump( weightedRandom( opts ) );
}
I'm looking for better solutions, please suggest improvements or alternatives.
Rejection sampling (such as in your solution) is the first thing that comes to mind, whereby you build a lookup table with elements populated by their weight distribution, then pick a random location in the table and return it. As an implementation choice, I would make a higher order function which takes a spec and returns a function which returns values based on the distribution in the spec, this way you avoid having to build the table for each call. The downsides are that the algorithmic performance of building the table is linear by the number of items and there could potentially be a lot of memory usage for large specs (or those with members with very small or precise weights, e.g. {0:0.99999, 1:0.00001}). The upside is that picking a value has constant time, which might be desirable if performance is critical. In JavaScript:
function weightedRand(spec) {
var i, j, table=[];
for (i in spec) {
// The constant 10 below should be computed based on the
// weights in the spec for a correct and optimal table size.
// E.g. the spec {0:0.999, 1:0.001} will break this impl.
for (j=0; j<spec[i]*10; j++) {
table.push(i);
}
}
return function() {
return table[Math.floor(Math.random() * table.length)];
}
}
var rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1});
rand012(); // random in distribution...
Another strategy is to pick a random number in [0,1) and iterate over the weight specification summing the weights, if the random number is less than the sum then return the associated value. Of course, this assumes that the weights sum to one. This solution has no up-front costs but has average algorithmic performance linear by the number of entries in the spec. For example, in JavaScript:
function weightedRand2(spec) {
var i, sum=0, r=Math.random();
for (i in spec) {
sum += spec[i];
if (r <= sum) return i;
}
}
weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution...
Generate a random number R between 0 and 1.
If R in [0, 0.1) -> 1
If R in [0.1, 0.2) -> 2
If R in [0.2, 1] -> 3
If you can't directly get a number between 0 and 1, generate a number in a range that will produce as much precision as you want. For example, if you have the weights for
(1, 83.7%) and (2, 16.3%), roll a number from 1 to 1000. 1-837 is a 1. 838-1000 is 2.
I use the following
function weightedRandom(min, max) {
return Math.round(max / (Math.random() * max + min));
}
This is my go-to "weighted" random, where I use an inverse function of "x" (where x is a random between min and max) to generate a weighted result, where the minimum is the most heavy element, and the maximum the lightest (least chances of getting the result)
So basically, using weightedRandom(1, 5) means the chances of getting a 1 are higher than a 2 which are higher than a 3, which are higher than a 4, which are higher than a 5.
Might not be useful for your use case but probably useful for people googling this same question.
After a 100 iterations try, it gave me:
==================
| Result | Times |
==================
| 1 | 55 |
| 2 | 28 |
| 3 | 8 |
| 4 | 7 |
| 5 | 2 |
==================
Here are 3 solutions in javascript since I'm not sure which language you want it in. Depending on your needs one of the first two might work, but the the third one is probably the easiest to implement with large sets of numbers.
function randomSimple(){
return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
}
function randomCase(){
var n=Math.floor(Math.random()*100)
switch(n){
case n<80:
return 0;
case n<90:
return 1;
case n<100:
return 2;
}
}
function randomLoop(weight,num){
var n=Math.floor(Math.random()*100),amt=0;
for(var i=0;i<weight.length;i++){
//amt+=weight[i]; *alternative method
//if(n<amt){
if(n<weight[i]){
return num[i];
}
}
}
weight=[80,90,100];
//weight=[80,10,10]; *alternative method
num=[0,1,2]
8 years late but here's my solution in 4 lines.
Prepare an array of probability mass function such that
pmf[array_index] = P(X=array_index):
var pmf = [0.8, 0.1, 0.1]
Prepare an array for the corresponding cumulative distribution function such that
cdf[array_index] = F(X=array_index):
var cdf = pmf.map((sum => value => sum += value)(0))
// [0.8, 0.9, 1]
3a) Generate a random number.
3b) Get an array of elements that are more than or equal to this number.
3c) Return its length.
var r = Math.random()
cdf.filter(el => r >= el).length
This is more or less a generic-ized version of what #trinithis wrote, in Java: I did it with ints rather than floats to avoid messy rounding errors.
static class Weighting {
int value;
int weighting;
public Weighting(int v, int w) {
this.value = v;
this.weighting = w;
}
}
public static int weightedRandom(List<Weighting> weightingOptions) {
//determine sum of all weightings
int total = 0;
for (Weighting w : weightingOptions) {
total += w.weighting;
}
//select a random value between 0 and our total
int random = new Random().nextInt(total);
//loop thru our weightings until we arrive at the correct one
int current = 0;
for (Weighting w : weightingOptions) {
current += w.weighting;
if (random < current)
return w.value;
}
//shouldn't happen.
return -1;
}
public static void main(String[] args) {
List<Weighting> weightings = new ArrayList<Weighting>();
weightings.add(new Weighting(0, 8));
weightings.add(new Weighting(1, 1));
weightings.add(new Weighting(2, 1));
for (int i = 0; i < 100; i++) {
System.out.println(weightedRandom(weightings));
}
}
How about
int [ ] numbers = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 } ;
then you can randomly select from numbers and 0 will have an 80% chance, 1 10%, and 2 10%
This one is in Mathematica, but it's easy to copy to another language, I use it in my games and it can handle decimal weights:
weights = {0.5,1,2}; // The weights
weights = N#weights/Total#weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length#weights, i++,
If[random >= min && random < max,
Print["Chosen index number: " <> ToString#i]
];
min += weights[[i]];
If[i == Length#weights,
max = 1,
max += weights[[i + 1]]
]
]
(Now I'm talking with a lists first element's index equals 0) The idea behind this is that having a normalized list weights there is a chance of weights[n] to return the index n, so the distances between the min and max at step n should be weights[n]. The total distance from the minimum min (which we put it to be 0) and the maximum max is the sum of the list weights.
The good thing behind this is that you don't append to any array or nest for loops, and that increases heavily the execution time.
Here is the code in C# without needing to normalize the weights list and deleting some code:
int WeightedRandom(List<float> weights) {
float total = 0f;
foreach (float weight in weights) {
total += weight;
}
float max = weights [0],
random = Random.Range(0f, total);
for (int index = 0; index < weights.Count; index++) {
if (random < max) {
return index;
} else if (index == weights.Count - 1) {
return weights.Count-1;
}
max += weights[index+1];
}
return -1;
}
I suggest to use a continuous check of the probability and the rest of the random number.
This function sets first the return value to the last possible index and iterates until the rest of the random value is smaller than the actual probability.
The probabilities have to sum to one.
function getRandomIndexByProbability(probabilities) {
var r = Math.random(),
index = probabilities.length - 1;
probabilities.some(function (probability, i) {
if (r < probability) {
index = i;
return true;
}
r -= probability;
});
return index;
}
var i,
probabilities = [0.8, 0.1, 0.1],
count = probabilities.map(function () { return 0; });
for (i = 0; i < 1e6; i++) {
count[getRandomIndexByProbability(probabilities)]++;
}
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Thanks all, this was a helpful thread. I encapsulated it into a convenience function (Typescript). Tests below (sinon, jest). Could definitely be a bit tighter, but hopefully it's readable.
export type WeightedOptions = {
[option: string]: number;
};
// Pass in an object like { a: 10, b: 4, c: 400 } and it'll return either "a", "b", or "c", factoring in their respective
// weight. So in this example, "c" is likely to be returned 400 times out of 414
export const getRandomWeightedValue = (options: WeightedOptions) => {
const keys = Object.keys(options);
const totalSum = keys.reduce((acc, item) => acc + options[item], 0);
let runningTotal = 0;
const cumulativeValues = keys.map((key) => {
const relativeValue = options[key]/totalSum;
const cv = {
key,
value: relativeValue + runningTotal
};
runningTotal += relativeValue;
return cv;
});
const r = Math.random();
return cumulativeValues.find(({ key, value }) => r <= value)!.key;
};
Tests:
describe('getRandomWeightedValue', () => {
// Out of 1, the relative and cumulative values for these are:
// a: 0.1666 -> 0.16666
// b: 0.3333 -> 0.5
// c: 0.5 -> 1
const values = { a: 10, b: 20, c: 30 };
it('returns appropriate values for particular random value', () => {
// any random number under 0.166666 should return "a"
const stub1 = sinon.stub(Math, 'random').returns(0);
const result1 = randomUtils.getRandomWeightedValue(values);
expect(result1).toEqual('a');
stub1.restore();
const stub2 = sinon.stub(Math, 'random').returns(0.1666);
const result2 = randomUtils.getRandomWeightedValue(values);
expect(result2).toEqual('a');
stub2.restore();
// any random number between 0.166666 and 0.5 should return "b"
const stub3 = sinon.stub(Math, 'random').returns(0.17);
const result3 = randomUtils.getRandomWeightedValue(values);
expect(result3).toEqual('b');
stub3.restore();
const stub4 = sinon.stub(Math, 'random').returns(0.3333);
const result4 = randomUtils.getRandomWeightedValue(values);
expect(result4).toEqual('b');
stub4.restore();
const stub5 = sinon.stub(Math, 'random').returns(0.5);
const result5 = randomUtils.getRandomWeightedValue(values);
expect(result5).toEqual('b');
stub5.restore();
// any random number above 0.5 should return "c"
const stub6 = sinon.stub(Math, 'random').returns(0.500001);
const result6 = randomUtils.getRandomWeightedValue(values);
expect(result6).toEqual('c');
stub6.restore();
const stub7 = sinon.stub(Math, 'random').returns(1);
const result7 = randomUtils.getRandomWeightedValue(values);
expect(result7).toEqual('c');
stub7.restore();
});
});
Shortest solution in modern JavaScript
Note: all weights need to be integers
function weightedRandom(items){
let table = Object.entries(items)
.flatMap(([item, weight]) => Array(item).fill(weight))
return table[Math.floor(Math.random() * table.length)]
}
const key = weightedRandom({
"key1": 1,
"key2": 4,
"key3": 8
}) // returns e.g. "key1"
here is the input and ratios : 0 (80%), 1(10%) , 2 (10%)
lets draw them out so its easy to visualize.
0 1 2
-------------------------------------________+++++++++
lets add up the total weight and call it TR for total ratio. so in this case 100.
lets randomly get a number from (0-TR) or (0 to 100 in this case) . 100 being your weights total. Call it RN for random number.
so now we have TR as the total weight and RN as the random number between 0 and TR.
so lets imagine we picked a random # from 0 to 100. Say 21. so thats actually 21%.
WE MUST CONVERT/MATCH THIS TO OUR INPUT NUMBERS BUT HOW ?
lets loop over each weight (80, 10, 10) and keep the sum of the weights we already visit.
the moment the sum of the weights we are looping over is greater then the random number RN (21 in this case), we stop the loop & return that element position.
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 21) //(80 > 21) so break on first pass
break;
}
//position will be 0 so we return array[0]--> 0
lets say the random number (between 0 and 100) is 83. Lets do it again:
double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 83) //(90 > 83) so break
break;
}
//we did two passes in the loop so position is 1 so we return array[1]---> 1
I have a slotmachine and I used the code below to generate random numbers. In probabilitiesSlotMachine the keys are the output in the slotmachine, and the values represent the weight.
const probabilitiesSlotMachine = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}]
var allSlotMachineResults = []
probabilitiesSlotMachine.forEach(function(obj, index){
for (var key in obj){
for (var loop = 0; loop < obj[key]; loop ++){
allSlotMachineResults.push(key)
}
}
});
Now to generate a random output, I use this code:
const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]
Enjoy the O(1) (constant time) solution for your problem.
If the input array is small, it can be easily implemented.
const number = Math.floor(Math.random() * 99); // Generate a random number from 0 to 99
let element;
if (number >= 0 && number <= 79) {
/*
In the range of 0 to 99, every number has equal probability
of occurring. Therefore, if you gather 80 numbers (0 to 79) and
make a "sub-group" of them, then their probabilities will get added.
Hence, what you get is an 80% chance that the number will fall in this
range.
So, quite naturally, there is 80% probability that this code will run.
Now, manually choose / assign element of your array to this variable.
*/
element = 0;
}
else if (number >= 80 && number <= 89) {
// 10% chance that this code runs.
element = 1;
}
else if (number >= 90 && number <= 99) {
// 10% chance that this code runs.
element = 2;
}
I'm having a trouble to get an array with numbers from 1 to 16 randomly without repetition.
I made num array to put numbers from createNum function.
The createNum function has a for loop which gets numbers from 1 to 16 until if statement push 16 numbers into num array.
At the end, I run createNum() and display numbers on the web.
While I'm making this code it sometimes worked but now it's not working.
can somebody point out where I made mistakes?
let num = [];
function createNum () {
for (i = 0; i <= 15;) {
let numGen = Math.floor(Math.random() * 15) + 1;
if (!num.includes(numGen)) {
num.push(numGen);
i++;
};
};
}
console.log(createNum());
document.getElementById("selectedNumbersShownHere").innerHTML = num;
console.log(num);
This is because Math.random() never returns 1, so at the end Math.floor(Math.random() * 15) will returns maximum 14 and adding it to 1 will get you maximum 15.
Use Math.ceil instead of Math.floor i.e
let num = [];
function createNum () {
for (i = 0; i <=15;) {
let numGen = Math.ceil(Math.random() * 16);
console.log(numGen)
if (!num.includes(numGen)) {
num.push(numGen);
i++;
};
};
}
console.log(createNum());
document.getElementById("selectedNumbersShownHere").innerHTML = num;
console.log(num);
Hope it helps!
for (i = 0; i <= 15;) generates 16 numbers, but Math.floor(Math.random() * 15) + 1 only have 15 possible values (1~15).
A shuffle function is recommended. Your function would be slow if you generate a large size shuffled array.
How can I shuffle an array?
Your loop seems never finish because the probability to get the last value is very low, and never can happen in a short time.
Plus :
your formula is wrong : let numGen = Math.floor(Math.random() * 15) + 1;
and should be................: let numGen = Math.floor(Math.random() * 16) + 1; value 16
see => Generating random whole numbers in JavaScript in a specific range?
do this:
function createNum()
{
let num =[], lastOne =136; // 136 = 1 +2 +3 + ... +16
for (;;)
{
let numGen = Math.floor(Math.random() *16) +1;
if (!num.includes(numGen))
{
lastOne -= numGen;
if (num.push(numGen)>14) break;
}
}
num.push(lastOne); // add the missing one (optimizing)
return num;
}
let unOrdered_16_vals = createNum();
/*
document.getElementById("selectedNumbersShownHere").textContent = unOrdered_16_vals.join('');
*/
console.log( JSON.stringify( unOrdered_16_vals ), 'length=', unOrdered_16_vals.length );
console.log( 'in order = ', JSON.stringify( unOrdered_16_vals.sort((a,b)=>a-b) ) );
remark : The push() method adds one or more elements to the end of an array and returns the new length of the array.
The problem in your code is that your looking for 16 distinct numbers out of 15 possible values.
The reason for this is that Math.floor(Math.random() * 15) + 1; will only return values between 1 and 15, but your loop will run until you have 16 unique values, so you enter into an infinite loop.
What you're basically trying to achieve is randomly shuffle an array with values from 1 to 16.
One common solution with good performance (O(n)) is the so-called Fisher-Yates shuffle. Here is code that addresses your requirements based on an implementation by Mike Bostock:
function shuffle(array) {
let m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
// create array with values from 1 to 16
const array = Array(16).fill().map((_, i) => i + 1);
// shuffle
const shuffled = shuffle(array);
console.log(shuffled);
Compared to your approach and the approach of other answers to this question, the code above will only make 15 calls to the random number generator, while the others will make anywhere between 16 and an infinite number of calls(1).
(1) In probability theory, this is called the coupon collector's problem. For a value n of 16, on average 54 calls would have to be made to collect all 16 values.
Try like this :
let num = [];
function createNum () {
for (i = 0; num.length <= 17; i++) {
let numGen = Math.floor(Math.random() * 16) + 1;
if (!num.includes(numGen)) {
num.push(numGen);
};
};
}
console.log(createNum());
document.getElementById("selectedNumbersShownHere").innerHTML = num;
console.log(num);
Please find the working demo here
This is an infinite loop error. Because your loop variable "i" is always less than or equal to 15. and your i++ is inside the if statement. You can achieve it in multiple ways. Below is one sample.
let num = [];
function createNum () {
for (i = 0; i <= 15;) {
let numGen = Math.floor(Math.random() * 15) + 1;
if (!num.includes(numGen)) {
num.push(numGen);
};
i++;
};
}
console.log(createNum());
document.getElementById("selectedNumbersShownHere").innerHTML = num;
console.log(num);
sorry, I'm "passionate" about this question, and I can't resist presenting a second answer, which is IMHO: the best!
function createNum ()
{
let num = []
for (let len=0;len<16;)
{
let numGen = Math.ceil(Math.random() * 16)
if (!num.includes(numGen))
{ len = num.push(numGen) }
}
return num
}
let unOrdered = createNum();
console.log( JSON.stringify( unOrdered ) );
/*
document.getElementById("selectedNumbersShownHere").textContent = unOrdered_16_vals.join('');
*/
I am trying to build a random alphanumeric generator using javascript that will randomly generate a vehicle registration number, so the string MUST be in a specific format: three capital letters, three numbers, two capital letters. First three letters and numbers can be totally random i.e. ABC123, or GDS342. The last two letters are abbreviations for specific provinces/states i.e. MP, ZN, GP etc. An example: GDS342GP.
When a button is clicked on the webpage, the registration number should then be displayed in a textarea.
Any advise on how to accomplish this?
Thank you in advance.
String.fromCharCode() will give you a capital letter if you enter numbers from 65 to 90. So if you use this function 3 times with 3 random numbers between (and including) 65-90 you can generate three random capital letters:
const getRandomLetters = function(count) {
let acc = ''; // the resulting string (to return once results appended)
for(let i = 0; i < count; i++) { // generate amount
const randomCharCode = Math.floor(Math.random() * (91 - 65)) + 65;
acc += String.fromCharCode(randomCharCode);
}
return acc;
}
const characters = getRandomLetters(3);
console.log(characters);
To generate the three random numbers you can do this much in the same way. To do this you can use Math.floor(Math.random() * 10) to generate a random integer from 0 to 9. There are easier ways to do this, but this method will allow you to get numbers such as 000 or 050 which aren't in the hundreds but still considered three numbers:
const getRandomNumbers = function(count) {
let acc = '';
for(let i = 0; i < count; i++) {
acc += ~~(Math.random() * 10); // Note: ~~ is the same as Math.floor (just a little faster)
}
return acc;
}
const numbers = getRandomNumbers(3);
console.log(numbers);
Since you haven't specified how states are picked, I'll provide you with a way which picks them randomly.
You can store all your states in an array:
const states = ['MP', 'ZN', 'GP'];
And then pick a random number between (and including) zero to the length of your states array minus 1 to get a random index from this array. This will then allow you to access a random state by using this number as an index:
const states = ['MP', 'ZN', 'GP'];
const randomIndex = ~~(Math.random() * states.length); // random int from: [0, 3) -> gives ints: 0, 1, 2
const state = states[randomIndex];
console.log(state);
Now you can combine all of these ideas to generate your random string. You can also add an onclick method to your <button> element which will call a function when pressed. Also, you can also add an id to your <textarea> so that your javascript access it and change its value to be the generated string:
const getRandomLetters = function(count) {
let acc = ''; // the resulting string (to return once results appended)
for(let i = 0; i < count; i++) { // generate amount
let randomCharCode = Math.floor(Math.random() * (91 - 65)) + 65;
acc += String.fromCharCode(randomCharCode);
}
return acc;
}
const getRandomNumbers = function(count) {
let acc = '';
for(let i = 0; i < count; i++) {
acc += ~~(Math.random() * 10); // Note: ~~ is the same as Math.floor (just a little faster)
}
return acc;
}
const generatePlate = function() {
const states = ['MP', 'ZN', 'GP'];
const randomIndex = ~~(Math.random() * states.length); // random int from: [0, 3) -> gives ints: 0, 1, 2
const characters = getRandomLetters(3);
const numbers = getRandomNumbers(3);
const state = states[randomIndex];
const resultPlate = characters + numbers + state;
document.getElementById('output').value = resultPlate;
}
<button onclick="generatePlate()">Generate</button>
<br />
<textarea id="output"></textarea>
This question already has answers here:
How to get a number of random elements from an array?
(25 answers)
Closed 4 years ago.
I got 4 Math.random generators. Each picking 1 of the X objects from the array.
var randomItem1 = projects[Math.floor(Math.random()*projects.length)];
var randomItem2 = projects[Math.floor(Math.random()*projects.length)];
var randomItem3 = projects[Math.floor(Math.random()*projects.length)];
var randomItem4 = projects[Math.floor(Math.random()*projects.length)];
How can I write a function that prevents the Math.random to generate the same number as other Math.random generator.
My guess:
Creating a loop that loops through the var randomItem 1 till 4. If it finds 1 or more outputs to be the same, it will regenerate a new output for 1 or more the duplicated outputs.
Any other suggestions?
Edit: This is for a website.
Thanks for the interesting problem. I always used a library for this sort of thing so it was fun figuring it out.
var projects
var randomProjects
function getRandomProjects(projects, sampleSize) {
var projectsClone = projects.slice();
var randomItems = [];
while (sampleSize--) {
randomItems.push(
projectsClone.splice(Math.floor(Math.random()*projectsClone.length), 1)[0]
);
}
return randomItems;
}
projects = ['1st project', '2nd project', '3rd project', '4th project', '5th project', '6th project'];
randomProjects = getRandomProjects(projects, 4);
randomProjects.forEach(function(randomProject) {
console.log(randomProject);
});
The projectsClone.splice(...) deletes a random project from projectsClone and returns it as a single item in an array ([<project>]). Thus in the next iteration of the loop that value (<project>) can no longer be chosen.
However I would suggest using a library if you are using this in production code. For example losdash's _.sampleSize(projects, 4)
Update
Removed unneeded parts like Set, for loop, and added splice(). This function will mutate the original array due to splice(). Using splice() on an array of consecutive ordered numbers (even after shuffling) guarantees a set of unique numbers.
/**
* genRan(quantity, length)
*
* Need a given amount of unique random numbers.
*
* Given a number for how many random numbers are to be returned (#quantity) and a
* number that represents the range of consecutive numbers to extract the random
* numbers from (#length), this function will:
* - generate a full array of numbers with the length of #length
* - use shuffle() to shuffle the array to provide an array of randomly ordered numbers
* - splices the first #quantity numbers of the shuffled array
* - returns new array of unique random numbers
*
* #param {Number} quantity length of returned array.
* #param {Number} length length of an array of numbers.
*
* #return {Array} An array of unique random numbers.
*/
Demo
Details commented in Demo
var qty = 4
var len = 100;
function genRan(quantity, length) {
// Ensure that quantity is never greater than length
if (quantity > length) {
quantity = length;
}
// Create an array of consecutive numbers from 1 to N
var array = Array.from({
length: length
}, (value, key = 0) => key + 1);
// Run shuffle get the returned shuffled array
var shuffled = shuffle(array);
// return an array of N (quantity) random numbers
return shuffled.splice(0, quantity);
}
console.log(genRan(qty, len));
/* shuffle utility
|| Uses Fisher-Yates algorithm
*/
function shuffle(array) {
var i = 0;
var j = 0;
var temp = null;
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}