Related
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;
}
For example: There are four items in an array. I want to get one randomly, like this:
array items = [
"bike" //40% chance to select
"car" //30% chance to select
"boat" //15% chance to select
"train" //10% chance to select
"plane" //5% chance to select
]
Both answers above rely on methods that will get slow quickly, especially the accepted one.
function weighted_random(items, weights) {
var i;
for (i = 1; i < weights.length; i++)
weights[i] += weights[i - 1];
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return items[i];
}
I replaced my older ES6 solution with this one as of December 2020, as ES6 isn't supported in older browsers, and I personally think this one is more readable.
If you'd rather use objects with the properties item and weight:
function weighted_random(options) {
var i;
var weights = [options[0].weight];
for (i = 1; i < options.length; i++)
weights[i] = options[i].weight + weights[i - 1];
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return options[i].item;
}
Explanation:
I've made this diagram that shows how this works:
This diagram shows what happens when an input with the weights [5, 2, 8, 3] is given. By taking partial sums of the weights, you just need to find the first one that's as large as a random number, and that's the randomly chosen item.
If a random number is chosen right on the border of two weights, like with 7 and 15 in the diagram, we go with the longer one. This is because 0 can be chosen by Math.random but 1 can't, so we get a fair distribution. If we went with the shorter one, A could be chosen 6 out of 18 times (0, 1, 2, 3, 4), giving it a higher weight than it should have.
Some es6 approach, with wildcard handling:
const randomizer = (values) => {
let i, pickedValue,
randomNr = Math.random(),
threshold = 0;
for (i = 0; i < values.length; i++) {
if (values[i].probability === '*') {
continue;
}
threshold += values[i].probability;
if (threshold > randomNr) {
pickedValue = values[i].value;
break;
}
if (!pickedValue) {
//nothing found based on probability value, so pick element marked with wildcard
pickedValue = values.filter((value) => value.probability === '*');
}
}
return pickedValue;
}
Example usage:
let testValues = [{
value : 'aaa',
probability: 0.1
},
{
value : 'bbb',
probability: 0.3
},
{
value : 'ccc',
probability: '*'
}]
randomizer(testValues); // will return "aaa" in 10% calls,
//"bbb" in 30% calls, and "ccc" in 60% calls;
Here's a faster way of doing that then other answers suggested...
You can achieve what you want by:
dividing the 0-to-1 segment into sections for each element based on their probability (For example, an element with probability 60% will take 60% of the segment).
generating a random number and checking in which segment it lands.
STEP 1
make a prefix sum array for the probability array, each value in it will signify where its corresponding section ends.
For example:
If we have probabilities: 60% (0.6), 30%, 5%, 3%, 2%. the prefix sum array will be: [0.6,0.9,0.95,0.98,1]
so we will have a segment divided like this (approximately): [ | | ||]
STEP 2
generate a random number between 0 and 1, and find it's lower bound in the prefix sum array. the index you'll find is the index of the segment that the random number landed in
Here's how you can implement this method:
let obj = {
"Common": "60",
"Uncommon": "25",
"Rare": "10",
"Legendary": "0.01",
"Mythical": "0.001"
}
// turning object into array and creating the prefix sum array:
let sums = [0]; // prefix sums;
let keys = [];
for(let key in obj) {
keys.push(key);
sums.push(sums[sums.length-1] + parseFloat(obj[key])/100);
}
sums.push(1);
keys.push('NONE');
// Step 2:
function lowerBound(target, low = 0, high = sums.length - 1) {
if (low == high) {
return low;
}
const midPoint = Math.floor((low + high) / 2);
if (target < sums[midPoint]) {
return lowerBound(target, low, midPoint);
} else if (target > sums[midPoint]) {
return lowerBound(target, midPoint + 1, high);
} else {
return midPoint + 1;
}
}
function getRandom() {
return lowerBound(Math.random());
}
console.log(keys[getRandom()], 'was picked!');
hope you find this helpful.
Note:
(In Computer Science) the lower bound of a value in a list/array is the smallest element that is greater or equal to it. for example, array:[1,10,24,99] and value 12. the lower bound will be the element with value 24.
When the array is sorted from smallest to biggest (like in our case) finding the lower bound of every value can be done extremely quickly with binary searching (O(log(n))).
Here is a O(1) (constant time) algo to solve your problem.
Generate a random number from 0 to 99 (100 total numbers). If there are 40 numbers (0 to 39) in a given sub-range, then there is a 40% probability that the randomly chosen number will fall in this range. See the code below.
const number = Math.floor(Math.random() * 99); // 0 to 99
let element;
if (number >= 0 && number <= 39) {
// 40% chance that this code runs. Hence, it is a bike.
element = "bike";
}
else if (number >= 40 && number <= 69) {
// 30% chance that this code runs. Hence, it is a car.
element = "car";
}
else if (number >= 70 && number <= 84) {
// 15% chance that this code runs. Hence, it is a boat.
element = "boat";
}
else if (number >= 85 && number <= 94) {
// 10% chance that this code runs. Hence, it is a train.
element = "train";
}
else if (number >= 95 && number <= 99) {
// 5% chance that this code runs. Hence, it is a plane.
element = "plane";
}
Remember this, one Mathematical principle from elementary school? "All the numbers in a specified distribution have equal probability of being chosen randomly."
This tells us that each of the random numbers have equal probability of occurring in a specific range, no matter how large or small that range might be.
That's it. This should work!
I added my solution as a method that works well on smaller arrays (no caching):
static weight_random(arr, weight_field){
if(arr == null || arr === undefined){
return null;
}
const totals = [];
let total = 0;
for(let i=0;i<arr.length;i++){
total += arr[i][weight_field];
totals.push(total);
}
const rnd = Math.floor(Math.random() * total);
let selected = arr[0];
for(let i=0;i<totals.length;i++){
if(totals[i] > rnd){
selected = arr[i];
break;
}
}
return selected;
}
Run it like this (provide the array and the weight property):
const wait_items = [
{"w" : 20, "min_ms" : "5000", "max_ms" : "10000"},
{"w" : 20, "min_ms" : "10000", "max_ms" : "20000"},
{"w" : 20, "min_ms" : "40000", "max_ms" : "80000"}
]
const item = weight_random(wait_items, "w");
console.log(item);
ES2015 version of Radvylf Programs's answer
function getWeightedRandomItem(items) {
const weights = items.reduce((acc, item, i) => {
acc.push(item.weight + (acc[i - 1] || 0));
return acc;
}, []);
const random = Math.random() * weights[weights.length - 1];
return items[weights.findIndex((weight) => weight > random)];
}
And ES2022
function getWeightedRandomItem(items) {
const weights = items.reduce((acc, item, i) => {
acc.push(item.weight + (acc[i - 1] ?? 0));
return acc;
}, []);
const random = Math.random() * weights.at(-1);
return items[weights.findIndex((weight) => weight > random)];
}
Sure you can. Here's a simple code to do it:
// Object or Array. Which every you prefer.
var item = {
bike:40, // Weighted Probability
care:30, // Weighted Probability
boat:15, // Weighted Probability
train:10, // Weighted Probability
plane:5 // Weighted Probability
// The number is not really percentage. You could put whatever number you want.
// Any number less than 1 will never occur
};
function get(input) {
var array = []; // Just Checking...
for(var item in input) {
if ( input.hasOwnProperty(item) ) { // Safety
for( var i=0; i<input[item]; i++ ) {
array.push(item);
}
}
}
// Probability Fun
return array[Math.floor(Math.random() * array.length)];
}
console.log(get(item)); // See Console.
How can I generate some unique random numbers between 1 and 100 using JavaScript?
For example: To generate 8 unique random numbers and store them to an array, you can simply do this:
var arr = [];
while(arr.length < 8){
var r = Math.floor(Math.random() * 100) + 1;
if(arr.indexOf(r) === -1) arr.push(r);
}
console.log(arr);
Populate an array with the numbers 1 through 100.
Shuffle it.
Take the first 8 elements of the resulting array.
Modern JS Solution using Set (and average case O(n))
const nums = new Set();
while(nums.size !== 8) {
nums.add(Math.floor(Math.random() * 100) + 1);
}
console.log([...nums]);
Another approach is to generate an 100 items array with ascending numbers and sort it randomly. This leads actually to a really short and (in my opinion) simple snippet.
const numbers = Array(100).fill().map((_, index) => index + 1);
numbers.sort(() => Math.random() - 0.5);
console.log(numbers.slice(0, 8));
Generate permutation of 100 numbers and then choose serially.
Use Knuth Shuffle(aka the Fisher-Yates shuffle) Algorithm.
JavaScript:
function fisherYates ( myArray,stop_count ) {
var i = myArray.length;
if ( i == 0 ) return false;
int c = 0;
while ( --i ) {
var j = Math.floor( Math.random() * ( i + 1 ) );
var tempi = myArray[i];
var tempj = myArray[j];
myArray[i] = tempj;
myArray[j] = tempi;
// Edited thanks to Frerich Raabe
c++;
if(c == stop_count)return;
}
}
CODE COPIED FROM LINK.
EDIT:
Improved code:
function fisherYates(myArray,nb_picks)
{
for (i = myArray.length-1; i > 1 ; i--)
{
var r = Math.floor(Math.random()*i);
var t = myArray[i];
myArray[i] = myArray[r];
myArray[r] = t;
}
return myArray.slice(0,nb_picks);
}
Potential problem:
Suppose we have array of 100 numbers {e.g. [1,2,3...100]} and we stop swapping after 8 swaps;
then most of the times array will look like {1,2,3,76,5,6,7,8,...numbers here will be shuffled ...10}.
Because every number will be swapped with probability 1/100 so
prob. of swapping first 8 numbers is 8/100 whereas prob. of swapping other 92 is 92/100.
But if we run algorithm for full array then we are sure (almost)every entry is swapped.
Otherwise we face a question : which 8 numbers to choose?
The above techniques are good if you want to avoid a library, but depending if you would be alright with a library, I would suggest checking out Chance for generating random stuff in JavaScript.
Specifically to solve your question, using Chance it's as easy as:
// One line!
var uniques = chance.unique(chance.natural, 8, {min: 1, max: 100});
// Print it out to the document for this snippet so we can see it in action
document.write(JSON.stringify(uniques));
<script src="http://chancejs.com/chance.min.js"></script>
Disclaimer, as the author of Chance, I am a bit biased ;)
To avoid any long and unreliable shuffles, I'd do the following...
Generate an array that contains the number between 1 and 100, in order.
Generate a random number between 1 and 100
Look up the number at this index in the array and store in your results
Remove the elemnt from the array, making it one shorter
Repeat from step 2, but use 99 as the upper limit of the random number
Repeat from step 2, but use 98 as the upper limit of the random number
Repeat from step 2, but use 97 as the upper limit of the random number
Repeat from step 2, but use 96 as the upper limit of the random number
Repeat from step 2, but use 95 as the upper limit of the random number
Repeat from step 2, but use 94 as the upper limit of the random number
Repeat from step 2, but use 93 as the upper limit of the random number
Voila - no repeated numbers.
I may post some actual code later, if anybody is interested.
Edit: It's probably the competitive streak in me but, having seen the post by #Alsciende, I couldn't resist posting the code that I promised.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>8 unique random number between 1 and 100</title>
<script type="text/javascript" language="Javascript">
function pick(n, min, max){
var values = [], i = max;
while(i >= min) values.push(i--);
var results = [];
var maxIndex = max;
for(i=1; i <= n; i++){
maxIndex--;
var index = Math.floor(maxIndex * Math.random());
results.push(values[index]);
values[index] = values[maxIndex];
}
return results;
}
function go(){
var running = true;
do{
if(!confirm(pick(8, 1, 100).sort(function(a,b){return a - b;}))){
running = false;
}
}while(running)
}
</script>
</head>
<body>
<h1>8 unique random number between 1 and 100</h1>
<p><button onclick="go()">Click me</button> to start generating numbers.</p>
<p>When the numbers appear, click OK to generate another set, or Cancel to stop.</p>
</body>
I would do this:
function randomInt(min, max) {
return Math.round(min + Math.random()*(max-min));
}
var index = {}, numbers = [];
for (var i=0; i<8; ++i) {
var number;
do {
number = randomInt(1, 100);
} while (index.hasOwnProperty("_"+number));
index["_"+number] = true;
numbers.push(number);
}
delete index;
This is a very generic function I have written to generate random unique/non-unique integers for an array. Assume the last parameter to be true in this scenario for this answer.
/* Creates an array of random integers between the range specified
len = length of the array you want to generate
min = min value you require
max = max value you require
unique = whether you want unique or not (assume 'true' for this answer)
*/
function _arrayRandom(len, min, max, unique) {
var len = (len) ? len : 10,
min = (min !== undefined) ? min : 1,
max = (max !== undefined) ? max : 100,
unique = (unique) ? unique : false,
toReturn = [], tempObj = {}, i = 0;
if(unique === true) {
for(; i < len; i++) {
var randomInt = Math.floor(Math.random() * ((max - min) + min));
if(tempObj['key_'+ randomInt] === undefined) {
tempObj['key_'+ randomInt] = randomInt;
toReturn.push(randomInt);
} else {
i--;
}
}
} else {
for(; i < len; i++) {
toReturn.push(Math.floor(Math.random() * ((max - min) + min)));
}
}
return toReturn;
}
Here the 'tempObj' is a very useful obj since every random number generated will directly check in this tempObj if that key already exists, if not, then we reduce the i by one since we need 1 extra run since the current random number already exists.
In your case, run the following
_arrayRandom(8, 1, 100, true);
That's all.
Shuffling the numbers from 1 to 100 is the right basic strategy, but if you need only 8 shuffled numbers, there's no need to shuffle all 100 numbers.
I don't know Javascript very well, but I believe it's easy to create an array of 100 nulls quickly. Then, for 8 rounds, you swap the n'th element of the array (n starting at 0) with a randomly selected element from n+1 through 99. Of course, any elements not populated yet mean that the element would really have been the original index plus 1, so that's trivial to factor in. When you're done with the 8 rounds, the first 8 elements of your array will have your 8 shuffled numbers.
var arr = []
while(arr.length < 8){
var randomnumber=Math.ceil(Math.random()*100)
if(arr.indexOf(randomnumber) === -1){arr.push(randomnumber)}
}
document.write(arr);
shorter than other answers I've seen
Implementing this as a generator makes it pretty nice to work with. Note, this implementation differs from ones that require the entire input array to be shuffled first.
This sample function works lazily, giving you 1 random item per iteration up to N items you ask for. This is nice because if you just want 3 items from a list of 1000, you don't have to touch all 1000 items first.
// sample :: Integer -> [a] -> [a]
const sample = n => function* (xs) {
let ys = xs.slice(0);
let len = xs.length;
while (n > 0 && len > 0) {
let i = (Math.random() * len) >> 0;
yield ys.splice(i,1)[0];
n--; len--;
}
}
// example inputs
let items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// get 3 random items
for (let i of sample(3) (items))
console.log(i); // f g c
// partial application
const lotto = sample(3);
for (let i of lotto(numbers))
console.log(i); // 3 8 7
// shuffle an array
const shuffle = xs => Array.from(sample (Infinity) (xs))
console.log(shuffle(items)) // [b c g f d e a]
I chose to implement sample in a way that does not mutate the input array, but you could easily argue that a mutating implementation is favourable.
For example, the shuffle function might wish to mutate the original input array. Or you might wish to sample from the same input at various times, updating the input each time.
// sample :: Integer -> [a] -> [a]
const sample = n => function* (xs) {
let len = xs.length;
while (n > 0 && len > 0) {
let i = (Math.random() * len) >> 0;
yield xs.splice(i,1)[0];
n--; len--;
}
}
// deal :: [Card] -> [Card]
const deal = xs => Array.from(sample (2) (xs));
// setup a deck of cards (13 in this case)
// cards :: [Card]
let cards = 'A234567890JQK'.split('');
// deal 6 players 2 cards each
// players :: [[Card]]
let players = Array.from(Array(6), $=> deal(cards))
console.log(players);
// [K, J], [6, 0], [2, 8], [Q, 7], [5, 4], [9, A]
// `cards` has been mutated. only 1 card remains in the deck
console.log(cards);
// [3]
sample is no longer a pure function because of the array input mutation, but in certain circumstances (demonstrated above) it might make more sense.
Another reason I chose a generator instead of a function that just returns an array is because you may want to continue sampling until some specific condition.
Perhaps I want the first prime number from a list of 1,000,000 random numbers.
"How many should I sample?" – you don't have to specify
"Do I have to find all the primes first and then select a random prime?" – Nope.
Because we're working with a generator, this task is trivial
const randomPrimeNumber = listOfNumbers => {
for (let x of sample(Infinity) (listOfNumbers)) {
if (isPrime(x))
return x;
}
return NaN;
}
This will continuously sample 1 random number at a time, x, check if it's prime, then return x if it is. If the list of numbers is exhausted before a prime is found, NaN is returned.
Note:
This answer was originally shared on another question that was closed as a duplicate of this one. Because it's very different from the other solutions provided here, I've decided to share it here as well
var numbers = [];
for (let i = 0; i < 8; i++) {
let a = true,
n;
while(a) {
n = Math.floor(Math.random() * 100) + 1;
a = numbers.includes(n);
}
numbers.push(n);
}
console.log(numbers);
Same permutation algorithm as The Machine Charmer, but with a prototyped implementation. Better suited to large number of picks. Uses js 1.7 destructuring assignment if available.
// swaps elements at index i and j in array this
// swapping is easy on js 1.7 (feature detection)
Array.prototype.swap = (function () {
var i=0, j=1;
try { [i,j]=[j,i]; }
catch (e) {}
if(i) {
return function(i,j) {
[this[i],this[j]] = [this[j],this[i]];
return this;
}
} else {
return function(i,j) {
var temp = this[i];
this[i] = this[j];
this[j] = temp;
return this;
}
}
})();
// shuffles array this
Array.prototype.shuffle = function() {
for(var i=this.length; i>1; i--) {
this.swap(i-1, Math.floor(i*Math.random()));
}
return this;
}
// returns n unique random numbers between min and max
function pick(n, min, max) {
var a = [], i = max;
while(i >= min) a.push(i--);
return a.shuffle().slice(0,n);
}
pick(8,1,100);
Edit:
An other proposition, better suited to small number of picks, based on belugabob's answer. To guarantee uniqueness, we remove the picked numbers from the array.
// removes n random elements from array this
// and returns them
Array.prototype.pick = function(n) {
if(!n || !this.length) return [];
var i = Math.floor(this.length*Math.random());
return this.splice(i,1).concat(this.pick(n-1));
}
// returns n unique random numbers between min and max
function pick(n, min, max) {
var a = [], i = max;
while(i >= min) a.push(i--);
return a.pick(n);
}
pick(8,1,100);
for arrays with holes like this [,2,,4,,6,7,,]
because my problem was to fill these holes. So I modified it as per my need :)
the following modified solution worked for me :)
var arr = [,2,,4,,6,7,,]; //example
while(arr.length < 9){
var randomnumber=Math.floor(Math.random()*9+1);
var found=false;
for(var i=0;i<arr.length;i++){
if(arr[i]==randomnumber){found=true;break;}
}
if(!found)
for(k=0;k<9;k++)
{if(!arr[k]) //if it's empty !!MODIFICATION
{arr[k]=randomnumber; break;}}
}
alert(arr); //outputs on the screen
The best earlier answer is the answer by sje397. You will get as good random numbers as you can get, as quick as possible.
My solution is very similar to his solution. However, sometimes you want the random numbers in random order, and that is why I decided to post an answer. In addition, I provide a general function.
function selectKOutOfN(k, n) {
if (k>n) throw "k>n";
var selection = [];
var sorted = [];
for (var i = 0; i < k; i++) {
var rand = Math.floor(Math.random()*(n - i));
for (var j = 0; j < i; j++) {
if (sorted[j]<=rand)
rand++;
else
break;
}
selection.push(rand);
sorted.splice(j, 0, rand);
}
return selection;
}
alert(selectKOutOfN(8, 100));
Here is my ES6 version I cobbled together. I'm sure it can be a little more consolidated.
function randomArray(i, min, max) {
min = Math.ceil(min);
max = Math.floor(max);
let arr = Array.from({length: i}, () => Math.floor(Math.random()* (max - min)) + min);
return arr.sort();
}
let uniqueItems = [...new Set(randomArray(8, 0, 100))]
console.log(uniqueItems);
How about using object properties as a hash table? This way your best scenario is to only randomize 8 times. It would only be effective if you want a small part of the range of numbers. It's also much less memory intensive than Fisher-Yates because you don't have to allocate space for an array.
var ht={}, i=rands=8;
while ( i>0 || keys(ht).length<rands) ht[Math.ceil(Math.random()*100)]=i--;
alert(keys(ht));
I then found out that Object.keys(obj) is an ECMAScript 5 feature so the above is pretty much useless on the internets right now. Fear not, because I made it ECMAScript 3 compatible by adding a keys function like this.
if (typeof keys == "undefined")
{
var keys = function(obj)
{
props=[];
for (k in ht) if (ht.hasOwnProperty(k)) props.push(k);
return props;
}
}
var bombout=0;
var checkArr=[];
var arr=[];
while(arr.length < 8 && bombout<100){
bombout++;
var randomNumber=Math.ceil(Math.random()*100);
if(typeof checkArr[randomNumber] == "undefined"){
checkArr[randomNumber]=1;
arr.push(randomNumber);
}
}
// untested - hence bombout
if you need more unique you must generate a array(1..100).
var arr=[];
function generateRandoms(){
for(var i=1;i<=100;i++) arr.push(i);
}
function extractUniqueRandom()
{
if (arr.length==0) generateRandoms();
var randIndex=Math.floor(arr.length*Math.random());
var result=arr[randIndex];
arr.splice(randIndex,1);
return result;
}
function extractUniqueRandomArray(n)
{
var resultArr=[];
for(var i=0;i<n;i++) resultArr.push(extractUniqueRandom());
return resultArr;
}
above code is faster:
extractUniqueRandomArray(50)=>
[2, 79, 38, 59, 63, 42, 52, 22, 78, 50, 39, 77, 1, 88, 40, 23, 48, 84, 91, 49, 4, 54, 93, 36, 100, 82, 62, 41, 89, 12, 24, 31, 86, 92, 64, 75, 70, 61, 67, 98, 76, 80, 56, 90, 83, 44, 43, 47, 7, 53]
Adding another better version of same code (accepted answer) with JavaScript 1.6 indexOf function. Do not need to loop thru whole array every time you are checking the duplicate.
var arr = []
while(arr.length < 8){
var randomnumber=Math.ceil(Math.random()*100)
var found=false;
if(arr.indexOf(randomnumber) > -1){found=true;}
if(!found)arr[arr.length]=randomnumber;
}
Older version of Javascript can still use the version at top
PS: Tried suggesting an update to the wiki but it was rejected. I still think it may be useful for others.
This is my personal solution :
<script>
var i, k;
var numbers = new Array();
k = Math.floor((Math.random()*8));
numbers[0]=k;
for (var j=1;j<8;j++){
k = Math.floor((Math.random()*8));
i=0;
while (i < numbers.length){
if (numbers[i] == k){
k = Math.floor((Math.random()*8));
i=0;
}else {i++;}
}
numbers[j]=k;
}
for (var j=0;j<8;j++){
alert (numbers[j]);
}
</script>
It randomly generates 8 unique array values (between 0 and 7), then displays them using an alert box.
function getUniqueRandomNos() {
var indexedArrayOfRandomNo = [];
for (var i = 0; i < 100; i++) {
var randNo = Math.random();
indexedArrayOfRandomNo.push([i, randNo]);
}
indexedArrayOfRandomNo.sort(function (arr1, arr2) {
return arr1[1] - arr2[1]
});
var uniqueRandNoArray = [];
for (i = 0; i < 8; i++) {
uniqueRandNoArray.push(indexedArrayOfRandomNo[i][0]);
}
return uniqueRandNoArray;
}
I think this method is different from methods given in most of the answers, so I thought I might add an answer here (though the question was asked 4 years ago).
We generate 100 random numbers, and tag each of them with numbers from 1 to 100. Then we sort these tagged random numbers, and the tags get shuffled randomly. Alternatively, as needed in this question, one could do away with just finding top 8 of the tagged random numbers. Finding top 8 items is cheaper than sorting the whole array.
One must note here, that the sorting algorithm influences this algorithm. If the sorting algorithm used is stable, there is slight bias in favor of smaller numbers. Ideally, we would want the sorting algorithm to be unstable and not even biased towards stability (or instability) to produce an answer with perfectly uniform probability distribution.
This can handle generating upto 20 digit UNIQUE random number
JS
var generatedNumbers = [];
function generateRandomNumber(precision) { // input --> number precision in integer
if (precision <= 20) {
var randomNum = Math.round(Math.random().toFixed(precision) * Math.pow(10, precision));
if (generatedNumbers.indexOf(randomNum) > -1) {
if (generatedNumbers.length == Math.pow(10, precision))
return "Generated all values with this precision";
return generateRandomNumber(precision);
} else {
generatedNumbers.push(randomNum);
return randomNum;
}
} else
return "Number Precision shoould not exceed 20";
}
generateRandomNumber(1);
jsFiddle
This solution uses the hash which is much more performant O(1) than checking if the resides in the array. It has extra safe checks too. Hope it helps.
function uniqueArray(minRange, maxRange, arrayLength) {
var arrayLength = (arrayLength) ? arrayLength : 10
var minRange = (minRange !== undefined) ? minRange : 1
var maxRange = (maxRange !== undefined) ? maxRange : 100
var numberOfItemsInArray = 0
var hash = {}
var array = []
if ( arrayLength > (maxRange - minRange) ) throw new Error('Cannot generate unique array: Array length too high')
while(numberOfItemsInArray < arrayLength){
// var randomNumber = Math.floor(Math.random() * (maxRange - minRange + 1) + minRange)
// following line used for performance benefits
var randomNumber = (Math.random() * (maxRange - minRange + 1) + minRange) << 0
if (!hash[randomNumber]) {
hash[randomNumber] = true
array.push(randomNumber)
numberOfItemsInArray++
}
}
return array
}
document.write(uniqueArray(1, 100, 8))
You can also do it with a one liner like this:
[...((add, set) => add(set, add))((set, add) => set.size < 8 ? add(set.add(Math.floor(Math.random()*100) + 1), add) : set, new Set())]
getRandom (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
getNRandom (min, max, n) {
const numbers = []
if (min > max) {
return new Error('Max is gt min')
}
if (min === max) {
return [min]
}
if ((max - min) >= n) {
while (numbers.length < n) {
let rand = this.getRandom(min, max + 1)
if (numbers.indexOf(rand) === -1) {
numbers.push(rand)
}
}
}
if ((max - min) < n) {
for (let i = min; i <= max; i++) {
numbers.push(i)
}
}
return numbers
}
Using a Set is your fastest option. Here is a generic function for getting a unique random that uses a callback generator. Now it's fast and reusable.
// Get a unique 'anything'
let unique = new Set()
function getUnique(generator) {
let number = generator()
while (!unique.add(number)) {
number = generator()
}
return number;
}
// The generator. Return anything, not just numbers.
const between_1_100 = () => 1 + Math.floor(Math.random() * 100)
// Test it
for (var i = 0; i < 8; i++) {
const aNumber = getUnique(between_1_100)
}
// Dump the 'stored numbers'
console.log(Array.from(unique))
This is a implementation of Fisher Yates/Durstenfeld Shuffle, but without actual creation of a array thus reducing space complexity or memory needed, when the pick size is small compared to the number of elements available.
To pick 8 numbers from 100, it is not necessary to create a array of 100 elements.
Assuming a array is created,
From the end of array(100), get random number(rnd) from 1 to 100
Swap 100 and the random number rnd
Repeat step 1 with array(99)
If a array is not created, A hashMap may be used to remember the actual swapped positions. When the second random number generated is equal to the one of the previously generated numbers, the map provides the current value in that position rather than the actual value.
const getRandom_ = (start, end) => {
return Math.floor(Math.random() * (end - start + 1)) + start;
};
const getRealValue_ = (map, rnd) => {
if (map.has(rnd)) {
return getRealValue_(map, map.get(rnd));
} else {
return rnd;
}
};
const getRandomNumbers = (n, start, end) => {
const out = new Map();
while (n--) {
const rnd = getRandom_(start, end--);
out.set(getRealValue_(out, rnd), end + 1);
}
return [...out.keys()];
};
console.info(getRandomNumbers(8, 1, 100));
console.info(getRandomNumbers(8, 1, Math.pow(10, 12)));
console.info(getRandomNumbers(800000, 1, Math.pow(10, 15)));
Here is an example of random 5 numbers taken from a range of 0 to 100 (both 0 and 100 included) with no duplication.
let finals = [];
const count = 5; // Considering 5 numbers
const max = 100;
for(let i = 0; i < max; i++){
const rand = Math.round(Math.random() * max);
!finals.includes(rand) && finals.push(rand)
}
finals = finals.slice(0, count)
I am trying to solve this Kata from Codewars: https://www.codewars.com/kata/simple-fun-number-258-is-divisible-by-6/train/javascript
The idea is that a number (expressed as a string) with one digit replaced with *, such as "1047*66", will be inserted into a function. You must return an array in which the values are the original number with the * replaced with any digit that will produce a number divisive by 6. So given "1*0", the correct resulting array should be [120, 150, 180].
I have some code that is producing some correct results but erroring for others, and I can't figure out why. Here's the code:
function isDivisibleBy6(s) {
var results = [];
for(i=0;i<10;i++) {
var string = i.toString(); // Convert i to string, ready to be inserted into s
var array = Array.from(s); // Make an array from s
var index = array.indexOf("*"); // Find where * is in the array of s
array[index] = string; // Replace * with the string of i
var number = array.join(""); // Join all indexes of the s array back together. Now we should have
// a single number expressed as a string, with * replaced with i
parseInt(number, 10); // Convert the string to an integer
if((number % 6) == 0) {
results.push(number);
} // If the integer is divisible by 6, add the integer into the results array
}
return(results);
};
This code works with the above example and generally with all smaller numbers. But it is producing errors for larger numbers. For example, when s is "29070521868839*57", the output should be []. However, I am getting ['29070521868839257', '29070521868839557', '29070521868839857']. I can't figure out where this would be going wrong. Is anyone able to help?
The problem is that these numbers are larger than the Number.MAX_SAFE_INTEGER - the point when JavaScript numbers break down in terms of reliability:
var num = 29070521868839257;
console.log(num > Number.MAX_SAFE_INTEGER);
console.log(num % 6);
console.log(num)
The last log shows that the num actually has a different value than what we gave it. This is because 29070521868839257 simply cannot be represented by a JavaScript number, hence you get the closest possible value that can be represented and that's 29070521868839256.
So, after some point in numbers, all mathematical operations become unreliable as the very numbers are imprecise.
What you can do instead is ignore treating this whole as a number - treat it as a string and only apply the principles of divisibility. This makes the task vastly easier.
For a number to be divisible by 6 it has to cover two criteria:
it has to be divisible by 2.
to verify this, you can just get the very smallest digit and check if it's divisible by 2. For example in 29070521868839257 if we take 7, and check 7 % 2, we get 1 which means that it's odd. We don't need to consider the whole number.
it has to be divisible by 3.
to verify this, you can sum each of the digits and see if that sum is divisible by 3. If we sum all the digits in 29070521868839257 we get 2 + 9 + 0 + 7 + 0 + 5 + 2 + 1 + 8 + 6 + 8 + 8 + 3 + 9 + 2 + 5 + 7 = 82 which is not divisible by 3. If in doubt, we can sum the digits again, since the rule can be applied to any number with more than two digits: 8 + 2 = 10 and 1 + 0 = 1. That is still not divisible by 3.
So, if we apply these we can get something like:
function isDivisibleBy6(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
};
function isDivisibleBy2(s) {
var lastDigit = Number(s.slice(-1));
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return (sum % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839257"));
console.log(isDivisibleBy6("29070521868839256"));
These can even be recursively defined true to the nature of these rules:
function isDivisibleBy6(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
};
function isDivisibleBy2(s) {
if (s.length === 0) {
return false;
}
if (s.length > 1) {
return isDivisibleBy2(s.slice(-1));
}
var lastDigit = Number(s);
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
if (s.length === 0) {
return false;
}
if (s.length > 1) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return isDivisibleBy3(String(sum));
}
var num = Number(s);
return (num % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839257"));
console.log(isDivisibleBy6("29070521868839256"));
This is purely to demonstrate the rules of division and how they can be applied to strings. You have to create numbers that will be divisible by 6 and to do that, you have to replace an asterisk. The easiest way to do it is like you did - generate all possibilities (e.g., 1*0 will be 100, 110, 120, 130, 140, 150, 160, 170, 180, 190) and then filter out whatever is not divisible by 6:
function isDivisibleBy6(s) {
var allDigits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var allPossibleNumbers = allDigits.map(function(digit) {
return s.replace("*", digit);
});
var numbersDibisibleBySix = allPossibleNumbers.filter(function(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
})
return numbersDibisibleBySix;
};
function isDivisibleBy2(s) {
var lastDigit = Number(s.slice(-1));
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return (sum % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839*57"));
console.log(isDivisibleBy6("29070521868839*56"));
As a last note, this can be written more concisely by removing intermediate values and using arrow functions:
function isDivisibleBy6(s) {
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
.map(digit => s.replace("*", digit))
.filter(s => isDivisibleBy2(s) && isDivisibleBy3(s));
};
const isDivisibleBy2 = s => Number(s.slice(-1)) % 2 === 0;
const isDivisibleBy3 = s => s.split("")
.map(Number)
.reduce((a, b) => a + b) % 3 === 0
console.log(isDivisibleBy6("29070521868839*57"));
console.log(isDivisibleBy6("29070521868839*56"));
Sum of all digits is divisible by three and the last digit is divisible by two.
An approach:
Get the index of the star.
Get left and right string beside of the star.
Return early if the last digit is not divisible by two.
Take the sum of all digits.
Finally create an array with missing digits:
Start loop from either zero (sum has no rest with three) or take the delta of three and the rest (because you want a number which is divisible by three).
Go while value is smaller then ten.
Increase the value either by 3 or by 6, if the index of the star is the last character.
Take left, value and right part for pushing to the result set.
Return result.
function get6(s) {
var index = s.indexOf('*'),
left = s.slice(0, index),
right = s.slice(index + 1),
result = [],
sum = 0,
i, step;
if (s[s.length - 1] % 2) return [];
for (i = 0; i < s.length; i++) if (i !== index) sum += +s[i];
i = sum % 3 && 3 - sum % 3;
step = s.length - 1 === index ? 6 : 3;
for (; i < 10; i += step) result.push(left + i + right);
return result;
}
console.log(get6("*")); // ["0", "6"]
console.log(get6("10*")); // ["102", "108"]
console.log(get6("1*0")); // ["120", "150", "180"]
console.log(get6("*1")); // []
console.log(get6("1234567890123456789012345678*0")); // ["123456789012345678901234567800","123456789012345678901234567830","123456789012345678901234567860","123456789012345678901234567890"]
.as-console-wrapper { max-height: 100% !important; top: 0; }
The problem is with:
parseInt(number, 10);
You can check and see that when number is large enough, this result converted back to string is not equal to the original value of number, due to the limit on floating point precision.
This challenge can be solved without having to convert the given string to number. Instead use a property of numbers that are multiples of 6. They are multiples of 3 and even. Multiples of 3 have the property that the sum of the digits (in decimal representation) are also multiples of 3.
So start by checking that the last digit is not 1, 3, 5, 7, or 9, because in that case there is no solution.
Otherwise, sum up the digits (ignore the asterisk). Determine which value you still need to add to that sum to get to a multiple of 3. This will be 0, 1 or 2. If the asterisk is not at the far right, produce solutions with this digit, and 3, 6, 9 added to it (until you get double digits).
If the asterisk is at the far right, you can do the same, but you must make sure that you exclude odd digits in that position.
If you are desperate, here is a solution. But I hope you can make it work yourself.
function isDivisibleBy6(s) {
// If last digit is odd, it can never be divisable by 6
if ("13579".includes(s[s.length-1])) return [];
let [left, right] = s.split("*");
// Calculate the sum of the digits (ignore the asterisk)
let sum = 0;
for (let ch of s) sum += +ch || 0;
// What value remains to be added to make the digit-sum a multiple of 3?
sum = (3 - sum%3) % 3;
// When asterisk in last position, then solution digit are 6 apart, otherwise 3
let mod = right.length ? 3 : 6;
if (mod === 6 && sum % 2) sum += 3; // Don't allow odd digit at last position
// Build the solutions, by injecting the found digit values
let result = [];
for (; sum < 10; sum += mod) result.push(left + sum + right);
return result;
}
// Demo
console.log(isDivisibleBy6("1234567890123456789012345678*0"));
BigInt
There is also another way to get around the floating point precision problem: use BigInt instead of floating point. However, BigInt is not supported on CodeWars, at least not in that specific Kata, where the available version of Node goes up to 8.1.3, while BigInt was introduced only in Node 10.
function isDivisibleBy6(s) {
let [left, right] = s.split("*");
let result = [];
for (let i = 0; i < 10; i++) {
let k = BigInt(left + i + right);
if (k % 6n === 0n) result.push(k.toString());
}
return result;
}
// Demo
console.log(isDivisibleBy6("1234567890123456789012345678*0"));
This would anyway feel like "cheating" (if it were accepted), as it's clearly not the purpose of the Kata.
As mentioned, the values you are using are above the maximum integer value and therefore unsafe, please see the docmentation about this over here Number.MAX_SAFE_INTEGER. You can use BigInt(string) to use larger values.
Thanks for all the responses. I have now created successful code!
function isDivisibleBy6(s) {
var results = [];
for(i=0;i<10;i++) {
var string = i.toString();
var array = Array.from(s);
var index = array.indexOf("*");
array[index] = string;
var div2 = 0;
var div3 = 0;
if(parseInt((array[array.length-1]),10) % 2 == 0) {
div2 = 1;
}
var numarray = array.map((x) => parseInt(x));
if(numarray.reduce(function myFunc(acc, value) {return acc+value}) % 3 == 0) {
div3 = 1;
}
if(div2 == 1 && div3 == 1) {
results.push(array.join(""));
}
}
return(results);
};
I know this could be factored down quite a bit by merging the if expressions together, but I like to see things split out so that when I look back over previous solutions my thought process is clearer.
Thanks again for all the help!
How can I generate some unique random numbers between 1 and 100 using JavaScript?
For example: To generate 8 unique random numbers and store them to an array, you can simply do this:
var arr = [];
while(arr.length < 8){
var r = Math.floor(Math.random() * 100) + 1;
if(arr.indexOf(r) === -1) arr.push(r);
}
console.log(arr);
Populate an array with the numbers 1 through 100.
Shuffle it.
Take the first 8 elements of the resulting array.
Modern JS Solution using Set (and average case O(n))
const nums = new Set();
while(nums.size !== 8) {
nums.add(Math.floor(Math.random() * 100) + 1);
}
console.log([...nums]);
Another approach is to generate an 100 items array with ascending numbers and sort it randomly. This leads actually to a really short and (in my opinion) simple snippet.
const numbers = Array(100).fill().map((_, index) => index + 1);
numbers.sort(() => Math.random() - 0.5);
console.log(numbers.slice(0, 8));
Generate permutation of 100 numbers and then choose serially.
Use Knuth Shuffle(aka the Fisher-Yates shuffle) Algorithm.
JavaScript:
function fisherYates ( myArray,stop_count ) {
var i = myArray.length;
if ( i == 0 ) return false;
int c = 0;
while ( --i ) {
var j = Math.floor( Math.random() * ( i + 1 ) );
var tempi = myArray[i];
var tempj = myArray[j];
myArray[i] = tempj;
myArray[j] = tempi;
// Edited thanks to Frerich Raabe
c++;
if(c == stop_count)return;
}
}
CODE COPIED FROM LINK.
EDIT:
Improved code:
function fisherYates(myArray,nb_picks)
{
for (i = myArray.length-1; i > 1 ; i--)
{
var r = Math.floor(Math.random()*i);
var t = myArray[i];
myArray[i] = myArray[r];
myArray[r] = t;
}
return myArray.slice(0,nb_picks);
}
Potential problem:
Suppose we have array of 100 numbers {e.g. [1,2,3...100]} and we stop swapping after 8 swaps;
then most of the times array will look like {1,2,3,76,5,6,7,8,...numbers here will be shuffled ...10}.
Because every number will be swapped with probability 1/100 so
prob. of swapping first 8 numbers is 8/100 whereas prob. of swapping other 92 is 92/100.
But if we run algorithm for full array then we are sure (almost)every entry is swapped.
Otherwise we face a question : which 8 numbers to choose?
The above techniques are good if you want to avoid a library, but depending if you would be alright with a library, I would suggest checking out Chance for generating random stuff in JavaScript.
Specifically to solve your question, using Chance it's as easy as:
// One line!
var uniques = chance.unique(chance.natural, 8, {min: 1, max: 100});
// Print it out to the document for this snippet so we can see it in action
document.write(JSON.stringify(uniques));
<script src="http://chancejs.com/chance.min.js"></script>
Disclaimer, as the author of Chance, I am a bit biased ;)
To avoid any long and unreliable shuffles, I'd do the following...
Generate an array that contains the number between 1 and 100, in order.
Generate a random number between 1 and 100
Look up the number at this index in the array and store in your results
Remove the elemnt from the array, making it one shorter
Repeat from step 2, but use 99 as the upper limit of the random number
Repeat from step 2, but use 98 as the upper limit of the random number
Repeat from step 2, but use 97 as the upper limit of the random number
Repeat from step 2, but use 96 as the upper limit of the random number
Repeat from step 2, but use 95 as the upper limit of the random number
Repeat from step 2, but use 94 as the upper limit of the random number
Repeat from step 2, but use 93 as the upper limit of the random number
Voila - no repeated numbers.
I may post some actual code later, if anybody is interested.
Edit: It's probably the competitive streak in me but, having seen the post by #Alsciende, I couldn't resist posting the code that I promised.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>8 unique random number between 1 and 100</title>
<script type="text/javascript" language="Javascript">
function pick(n, min, max){
var values = [], i = max;
while(i >= min) values.push(i--);
var results = [];
var maxIndex = max;
for(i=1; i <= n; i++){
maxIndex--;
var index = Math.floor(maxIndex * Math.random());
results.push(values[index]);
values[index] = values[maxIndex];
}
return results;
}
function go(){
var running = true;
do{
if(!confirm(pick(8, 1, 100).sort(function(a,b){return a - b;}))){
running = false;
}
}while(running)
}
</script>
</head>
<body>
<h1>8 unique random number between 1 and 100</h1>
<p><button onclick="go()">Click me</button> to start generating numbers.</p>
<p>When the numbers appear, click OK to generate another set, or Cancel to stop.</p>
</body>
I would do this:
function randomInt(min, max) {
return Math.round(min + Math.random()*(max-min));
}
var index = {}, numbers = [];
for (var i=0; i<8; ++i) {
var number;
do {
number = randomInt(1, 100);
} while (index.hasOwnProperty("_"+number));
index["_"+number] = true;
numbers.push(number);
}
delete index;
This is a very generic function I have written to generate random unique/non-unique integers for an array. Assume the last parameter to be true in this scenario for this answer.
/* Creates an array of random integers between the range specified
len = length of the array you want to generate
min = min value you require
max = max value you require
unique = whether you want unique or not (assume 'true' for this answer)
*/
function _arrayRandom(len, min, max, unique) {
var len = (len) ? len : 10,
min = (min !== undefined) ? min : 1,
max = (max !== undefined) ? max : 100,
unique = (unique) ? unique : false,
toReturn = [], tempObj = {}, i = 0;
if(unique === true) {
for(; i < len; i++) {
var randomInt = Math.floor(Math.random() * ((max - min) + min));
if(tempObj['key_'+ randomInt] === undefined) {
tempObj['key_'+ randomInt] = randomInt;
toReturn.push(randomInt);
} else {
i--;
}
}
} else {
for(; i < len; i++) {
toReturn.push(Math.floor(Math.random() * ((max - min) + min)));
}
}
return toReturn;
}
Here the 'tempObj' is a very useful obj since every random number generated will directly check in this tempObj if that key already exists, if not, then we reduce the i by one since we need 1 extra run since the current random number already exists.
In your case, run the following
_arrayRandom(8, 1, 100, true);
That's all.
Shuffling the numbers from 1 to 100 is the right basic strategy, but if you need only 8 shuffled numbers, there's no need to shuffle all 100 numbers.
I don't know Javascript very well, but I believe it's easy to create an array of 100 nulls quickly. Then, for 8 rounds, you swap the n'th element of the array (n starting at 0) with a randomly selected element from n+1 through 99. Of course, any elements not populated yet mean that the element would really have been the original index plus 1, so that's trivial to factor in. When you're done with the 8 rounds, the first 8 elements of your array will have your 8 shuffled numbers.
var arr = []
while(arr.length < 8){
var randomnumber=Math.ceil(Math.random()*100)
if(arr.indexOf(randomnumber) === -1){arr.push(randomnumber)}
}
document.write(arr);
shorter than other answers I've seen
Implementing this as a generator makes it pretty nice to work with. Note, this implementation differs from ones that require the entire input array to be shuffled first.
This sample function works lazily, giving you 1 random item per iteration up to N items you ask for. This is nice because if you just want 3 items from a list of 1000, you don't have to touch all 1000 items first.
// sample :: Integer -> [a] -> [a]
const sample = n => function* (xs) {
let ys = xs.slice(0);
let len = xs.length;
while (n > 0 && len > 0) {
let i = (Math.random() * len) >> 0;
yield ys.splice(i,1)[0];
n--; len--;
}
}
// example inputs
let items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// get 3 random items
for (let i of sample(3) (items))
console.log(i); // f g c
// partial application
const lotto = sample(3);
for (let i of lotto(numbers))
console.log(i); // 3 8 7
// shuffle an array
const shuffle = xs => Array.from(sample (Infinity) (xs))
console.log(shuffle(items)) // [b c g f d e a]
I chose to implement sample in a way that does not mutate the input array, but you could easily argue that a mutating implementation is favourable.
For example, the shuffle function might wish to mutate the original input array. Or you might wish to sample from the same input at various times, updating the input each time.
// sample :: Integer -> [a] -> [a]
const sample = n => function* (xs) {
let len = xs.length;
while (n > 0 && len > 0) {
let i = (Math.random() * len) >> 0;
yield xs.splice(i,1)[0];
n--; len--;
}
}
// deal :: [Card] -> [Card]
const deal = xs => Array.from(sample (2) (xs));
// setup a deck of cards (13 in this case)
// cards :: [Card]
let cards = 'A234567890JQK'.split('');
// deal 6 players 2 cards each
// players :: [[Card]]
let players = Array.from(Array(6), $=> deal(cards))
console.log(players);
// [K, J], [6, 0], [2, 8], [Q, 7], [5, 4], [9, A]
// `cards` has been mutated. only 1 card remains in the deck
console.log(cards);
// [3]
sample is no longer a pure function because of the array input mutation, but in certain circumstances (demonstrated above) it might make more sense.
Another reason I chose a generator instead of a function that just returns an array is because you may want to continue sampling until some specific condition.
Perhaps I want the first prime number from a list of 1,000,000 random numbers.
"How many should I sample?" – you don't have to specify
"Do I have to find all the primes first and then select a random prime?" – Nope.
Because we're working with a generator, this task is trivial
const randomPrimeNumber = listOfNumbers => {
for (let x of sample(Infinity) (listOfNumbers)) {
if (isPrime(x))
return x;
}
return NaN;
}
This will continuously sample 1 random number at a time, x, check if it's prime, then return x if it is. If the list of numbers is exhausted before a prime is found, NaN is returned.
Note:
This answer was originally shared on another question that was closed as a duplicate of this one. Because it's very different from the other solutions provided here, I've decided to share it here as well
var numbers = [];
for (let i = 0; i < 8; i++) {
let a = true,
n;
while(a) {
n = Math.floor(Math.random() * 100) + 1;
a = numbers.includes(n);
}
numbers.push(n);
}
console.log(numbers);
Same permutation algorithm as The Machine Charmer, but with a prototyped implementation. Better suited to large number of picks. Uses js 1.7 destructuring assignment if available.
// swaps elements at index i and j in array this
// swapping is easy on js 1.7 (feature detection)
Array.prototype.swap = (function () {
var i=0, j=1;
try { [i,j]=[j,i]; }
catch (e) {}
if(i) {
return function(i,j) {
[this[i],this[j]] = [this[j],this[i]];
return this;
}
} else {
return function(i,j) {
var temp = this[i];
this[i] = this[j];
this[j] = temp;
return this;
}
}
})();
// shuffles array this
Array.prototype.shuffle = function() {
for(var i=this.length; i>1; i--) {
this.swap(i-1, Math.floor(i*Math.random()));
}
return this;
}
// returns n unique random numbers between min and max
function pick(n, min, max) {
var a = [], i = max;
while(i >= min) a.push(i--);
return a.shuffle().slice(0,n);
}
pick(8,1,100);
Edit:
An other proposition, better suited to small number of picks, based on belugabob's answer. To guarantee uniqueness, we remove the picked numbers from the array.
// removes n random elements from array this
// and returns them
Array.prototype.pick = function(n) {
if(!n || !this.length) return [];
var i = Math.floor(this.length*Math.random());
return this.splice(i,1).concat(this.pick(n-1));
}
// returns n unique random numbers between min and max
function pick(n, min, max) {
var a = [], i = max;
while(i >= min) a.push(i--);
return a.pick(n);
}
pick(8,1,100);
for arrays with holes like this [,2,,4,,6,7,,]
because my problem was to fill these holes. So I modified it as per my need :)
the following modified solution worked for me :)
var arr = [,2,,4,,6,7,,]; //example
while(arr.length < 9){
var randomnumber=Math.floor(Math.random()*9+1);
var found=false;
for(var i=0;i<arr.length;i++){
if(arr[i]==randomnumber){found=true;break;}
}
if(!found)
for(k=0;k<9;k++)
{if(!arr[k]) //if it's empty !!MODIFICATION
{arr[k]=randomnumber; break;}}
}
alert(arr); //outputs on the screen
The best earlier answer is the answer by sje397. You will get as good random numbers as you can get, as quick as possible.
My solution is very similar to his solution. However, sometimes you want the random numbers in random order, and that is why I decided to post an answer. In addition, I provide a general function.
function selectKOutOfN(k, n) {
if (k>n) throw "k>n";
var selection = [];
var sorted = [];
for (var i = 0; i < k; i++) {
var rand = Math.floor(Math.random()*(n - i));
for (var j = 0; j < i; j++) {
if (sorted[j]<=rand)
rand++;
else
break;
}
selection.push(rand);
sorted.splice(j, 0, rand);
}
return selection;
}
alert(selectKOutOfN(8, 100));
Here is my ES6 version I cobbled together. I'm sure it can be a little more consolidated.
function randomArray(i, min, max) {
min = Math.ceil(min);
max = Math.floor(max);
let arr = Array.from({length: i}, () => Math.floor(Math.random()* (max - min)) + min);
return arr.sort();
}
let uniqueItems = [...new Set(randomArray(8, 0, 100))]
console.log(uniqueItems);
How about using object properties as a hash table? This way your best scenario is to only randomize 8 times. It would only be effective if you want a small part of the range of numbers. It's also much less memory intensive than Fisher-Yates because you don't have to allocate space for an array.
var ht={}, i=rands=8;
while ( i>0 || keys(ht).length<rands) ht[Math.ceil(Math.random()*100)]=i--;
alert(keys(ht));
I then found out that Object.keys(obj) is an ECMAScript 5 feature so the above is pretty much useless on the internets right now. Fear not, because I made it ECMAScript 3 compatible by adding a keys function like this.
if (typeof keys == "undefined")
{
var keys = function(obj)
{
props=[];
for (k in ht) if (ht.hasOwnProperty(k)) props.push(k);
return props;
}
}
var bombout=0;
var checkArr=[];
var arr=[];
while(arr.length < 8 && bombout<100){
bombout++;
var randomNumber=Math.ceil(Math.random()*100);
if(typeof checkArr[randomNumber] == "undefined"){
checkArr[randomNumber]=1;
arr.push(randomNumber);
}
}
// untested - hence bombout
if you need more unique you must generate a array(1..100).
var arr=[];
function generateRandoms(){
for(var i=1;i<=100;i++) arr.push(i);
}
function extractUniqueRandom()
{
if (arr.length==0) generateRandoms();
var randIndex=Math.floor(arr.length*Math.random());
var result=arr[randIndex];
arr.splice(randIndex,1);
return result;
}
function extractUniqueRandomArray(n)
{
var resultArr=[];
for(var i=0;i<n;i++) resultArr.push(extractUniqueRandom());
return resultArr;
}
above code is faster:
extractUniqueRandomArray(50)=>
[2, 79, 38, 59, 63, 42, 52, 22, 78, 50, 39, 77, 1, 88, 40, 23, 48, 84, 91, 49, 4, 54, 93, 36, 100, 82, 62, 41, 89, 12, 24, 31, 86, 92, 64, 75, 70, 61, 67, 98, 76, 80, 56, 90, 83, 44, 43, 47, 7, 53]
Adding another better version of same code (accepted answer) with JavaScript 1.6 indexOf function. Do not need to loop thru whole array every time you are checking the duplicate.
var arr = []
while(arr.length < 8){
var randomnumber=Math.ceil(Math.random()*100)
var found=false;
if(arr.indexOf(randomnumber) > -1){found=true;}
if(!found)arr[arr.length]=randomnumber;
}
Older version of Javascript can still use the version at top
PS: Tried suggesting an update to the wiki but it was rejected. I still think it may be useful for others.
This is my personal solution :
<script>
var i, k;
var numbers = new Array();
k = Math.floor((Math.random()*8));
numbers[0]=k;
for (var j=1;j<8;j++){
k = Math.floor((Math.random()*8));
i=0;
while (i < numbers.length){
if (numbers[i] == k){
k = Math.floor((Math.random()*8));
i=0;
}else {i++;}
}
numbers[j]=k;
}
for (var j=0;j<8;j++){
alert (numbers[j]);
}
</script>
It randomly generates 8 unique array values (between 0 and 7), then displays them using an alert box.
function getUniqueRandomNos() {
var indexedArrayOfRandomNo = [];
for (var i = 0; i < 100; i++) {
var randNo = Math.random();
indexedArrayOfRandomNo.push([i, randNo]);
}
indexedArrayOfRandomNo.sort(function (arr1, arr2) {
return arr1[1] - arr2[1]
});
var uniqueRandNoArray = [];
for (i = 0; i < 8; i++) {
uniqueRandNoArray.push(indexedArrayOfRandomNo[i][0]);
}
return uniqueRandNoArray;
}
I think this method is different from methods given in most of the answers, so I thought I might add an answer here (though the question was asked 4 years ago).
We generate 100 random numbers, and tag each of them with numbers from 1 to 100. Then we sort these tagged random numbers, and the tags get shuffled randomly. Alternatively, as needed in this question, one could do away with just finding top 8 of the tagged random numbers. Finding top 8 items is cheaper than sorting the whole array.
One must note here, that the sorting algorithm influences this algorithm. If the sorting algorithm used is stable, there is slight bias in favor of smaller numbers. Ideally, we would want the sorting algorithm to be unstable and not even biased towards stability (or instability) to produce an answer with perfectly uniform probability distribution.
This can handle generating upto 20 digit UNIQUE random number
JS
var generatedNumbers = [];
function generateRandomNumber(precision) { // input --> number precision in integer
if (precision <= 20) {
var randomNum = Math.round(Math.random().toFixed(precision) * Math.pow(10, precision));
if (generatedNumbers.indexOf(randomNum) > -1) {
if (generatedNumbers.length == Math.pow(10, precision))
return "Generated all values with this precision";
return generateRandomNumber(precision);
} else {
generatedNumbers.push(randomNum);
return randomNum;
}
} else
return "Number Precision shoould not exceed 20";
}
generateRandomNumber(1);
jsFiddle
This solution uses the hash which is much more performant O(1) than checking if the resides in the array. It has extra safe checks too. Hope it helps.
function uniqueArray(minRange, maxRange, arrayLength) {
var arrayLength = (arrayLength) ? arrayLength : 10
var minRange = (minRange !== undefined) ? minRange : 1
var maxRange = (maxRange !== undefined) ? maxRange : 100
var numberOfItemsInArray = 0
var hash = {}
var array = []
if ( arrayLength > (maxRange - minRange) ) throw new Error('Cannot generate unique array: Array length too high')
while(numberOfItemsInArray < arrayLength){
// var randomNumber = Math.floor(Math.random() * (maxRange - minRange + 1) + minRange)
// following line used for performance benefits
var randomNumber = (Math.random() * (maxRange - minRange + 1) + minRange) << 0
if (!hash[randomNumber]) {
hash[randomNumber] = true
array.push(randomNumber)
numberOfItemsInArray++
}
}
return array
}
document.write(uniqueArray(1, 100, 8))
You can also do it with a one liner like this:
[...((add, set) => add(set, add))((set, add) => set.size < 8 ? add(set.add(Math.floor(Math.random()*100) + 1), add) : set, new Set())]
getRandom (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
getNRandom (min, max, n) {
const numbers = []
if (min > max) {
return new Error('Max is gt min')
}
if (min === max) {
return [min]
}
if ((max - min) >= n) {
while (numbers.length < n) {
let rand = this.getRandom(min, max + 1)
if (numbers.indexOf(rand) === -1) {
numbers.push(rand)
}
}
}
if ((max - min) < n) {
for (let i = min; i <= max; i++) {
numbers.push(i)
}
}
return numbers
}
Using a Set is your fastest option. Here is a generic function for getting a unique random that uses a callback generator. Now it's fast and reusable.
// Get a unique 'anything'
let unique = new Set()
function getUnique(generator) {
let number = generator()
while (!unique.add(number)) {
number = generator()
}
return number;
}
// The generator. Return anything, not just numbers.
const between_1_100 = () => 1 + Math.floor(Math.random() * 100)
// Test it
for (var i = 0; i < 8; i++) {
const aNumber = getUnique(between_1_100)
}
// Dump the 'stored numbers'
console.log(Array.from(unique))
This is a implementation of Fisher Yates/Durstenfeld Shuffle, but without actual creation of a array thus reducing space complexity or memory needed, when the pick size is small compared to the number of elements available.
To pick 8 numbers from 100, it is not necessary to create a array of 100 elements.
Assuming a array is created,
From the end of array(100), get random number(rnd) from 1 to 100
Swap 100 and the random number rnd
Repeat step 1 with array(99)
If a array is not created, A hashMap may be used to remember the actual swapped positions. When the second random number generated is equal to the one of the previously generated numbers, the map provides the current value in that position rather than the actual value.
const getRandom_ = (start, end) => {
return Math.floor(Math.random() * (end - start + 1)) + start;
};
const getRealValue_ = (map, rnd) => {
if (map.has(rnd)) {
return getRealValue_(map, map.get(rnd));
} else {
return rnd;
}
};
const getRandomNumbers = (n, start, end) => {
const out = new Map();
while (n--) {
const rnd = getRandom_(start, end--);
out.set(getRealValue_(out, rnd), end + 1);
}
return [...out.keys()];
};
console.info(getRandomNumbers(8, 1, 100));
console.info(getRandomNumbers(8, 1, Math.pow(10, 12)));
console.info(getRandomNumbers(800000, 1, Math.pow(10, 15)));
Here is an example of random 5 numbers taken from a range of 0 to 100 (both 0 and 100 included) with no duplication.
let finals = [];
const count = 5; // Considering 5 numbers
const max = 100;
for(let i = 0; i < max; i++){
const rand = Math.round(Math.random() * max);
!finals.includes(rand) && finals.push(rand)
}
finals = finals.slice(0, count)