Randomly fill fixed number of cells in a two dimensional grid [closed] - javascript

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I'm trying to make JavaScript Minesweeper and I've got some problems in pushBombs function, I guess. I'm trying to put bombs in array, sometimes it puts exact number that is said but sometimes it puts less number.
My questions:
When condition is true it puts bomb in array but when it's false it passes this itaration or looks for another index in array that is true?
Math.random() returns different values in condition and in code block.
It puts bomb where it already was but then why?
var grid = {
_grid: [],
createGrid: function (c) {
for (var i = 0; i < c; i++) {
this._grid.push([]);
for (var a = 0; a < c; a++) {
this._grid[i][a] = 'Empty';
}
}
},
pushBombs: function (b) {
for (var i = 0; i < b; i++) {
if (this._grid[Math.round(Math.random() * (this._grid.length - 1))][Math.round(Math.random() * (this._grid.length - 1))] == 'Empty') {
this._grid[Math.round(Math.random() * (this._grid.length - 1))][Math.round(Math.random() * (this._grid.length - 1))] = 'Bomb';
}
}
},

Everytime you call Math.random() you are going to get a different value. Assuming that during your code, you want to be looking at the same slot in both the check and in the assignment, you would want to only call that once for each axis of your grid. Below, I'm calling them xx and yy.
pushBombs: function (b) {
for (var i = 0; i < b; i++) {
var xx = Math.round(Math.random() * (this._grid.length - 1));
var yy = Math.round(Math.random() * (this._grid.length - 1));
if (this._grid[xx][yy] == 'Empty') {
this._grid[xx][yy] = 'Bomb';
}
}
}
EDIT
Something you might want to think about is that, this code assumes that both dimensions of your grid are equal in length. Based on the createGrid() method it looks like that is the case. Thinking ahead you may choose to have a rectangular grid, in which case, this pushBombs() method would break. One option is to store the X and Y lengths in so that the xx and yy can correctly randomize their values. Alternatively, I might suggest changing the code above slightly to use:
//randmomize the length of the first dimension
var xx = Math.round(Math.random() * (this._grid.length - 1));
//randomize the second dimension. Notice, this is looking at the length
//of the first item in the grid (which is your second dimension).
var yy = Math.round(Math.random() * (this._grid[0].length - 1));
EDIT 2
If you want to ensure that you always get the exact number of bombs, you should use a while loop as #PierreDuc suggested:
while(b > 0) {
var xx = Math.round(Math.random() * (this._grid.length - 1));
var yy = Math.round(Math.random() * (this._grid[0].length - 1));
if (this._grid[xx][yy] == 'Empty') {
this._grid[xx][yy] = 'Bomb';
b--;
}
}
Note though that if the value of b is greater than the number of Empty slots, this will turn into an infinite loop.

Related

How to optimize code for HackerRank's Fraudulent Activity Notification problem

I have been working to solve this problem on the HackerRank site: Fraudulent Activity Notifications.
Below is the code I have written which satisfies the three sample test cases; however, it does not satisfy the larger test cases since it seems to take longer than 10 seconds.
The 10 second constraint is taken from here: HackerRank Environment.
function activityNotifications(expenditure, d) {
let notifications = 0;
let tmp = [];
let median = 0, medianEven = 0, iOfMedian = 0;
// Begin looping thru 'expenditure'
for(let i = 0; i < expenditure.length; i++) {
// slice from 'expenditure' beginning at 'i' and ending at 'i + d' where d = number of days
// sort 'tmp' in ascending order after
tmp = expenditure.slice(i, i + d);
tmp.sort();
// edge case, make sure we do not exceed boundaries of 'expenditure'
if((i + d) < expenditure.length) {
// if length of 'tmp' is divisible by 2, then we have an even length
// compute median accordingly
if(tmp.length % 2 == 0) {
medianEven = tmp.length / 2;
median = (tmp[medianEven - 1] + tmp[medianEven]) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * median)) {
notifications++;
}
}
// otherwise, we have an odd length of numbers
// therefore, compute median accordingly
else {
iOfMedian = (tmp.length + 1) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * tmp[iOfMedian - 1])) {
notifications++;
}
}
}
}
return notifications;
}
I am familiar with O notation for computing time complexity, so initially it seems the problem is either the excessive amount of variables declared or conditional statements used. Only one for loop is being used so I don't think the loop is where I should look to optimize the code. Unless, of course, we were to include the .sort() function used on 'tmp' which would definitely add to the time it takes to compute efficiently.
Is there anything I have not realized which is causing the code to take longer than expected? Any other hints would be greatly appreciated, thanks.

Trying to optimize my code to either remove nested loop or make it more efficient

A friend of mine takes a sequence of numbers from 1 to n (where n > 0)
Within that sequence, he chooses two numbers, a and b
He says that the product of a and b should be equal to the sum of all numbers in the sequence, excluding a and b
Given a number n, could you tell me the numbers he excluded from the sequence?
Have found the solution to this Kata from Code Wars but it times out (After 12 seconds) in the editor when I run it; any ideas as too how I should further optimize the nested for loop and or remove it?
function removeNb(n) {
var nArray = [];
var sum = 0;
var answersArray = [];
for (let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
var length = nArray.length;
for (let i = Math.round(n / 2); i < length; i++) {
for (let y = Math.round(n / 2); y < length; y++) {
if (i != y) {
if (i * y === sum - i - y) {
answersArray.push([i, y]);
break;
}
}
}
}
return answersArray;
}
console.log(removeNb(102));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think there is no reason for calculating the sum after you fill the array, you can do that while filling it.
function removeNb(n) {
let nArray = [];
let sum = 0;
for(let i = 1; i <= n; i++) {
nArray.push(i);
sum += i;
}
}
And since there could be only two numbers a and b as the inputs for the formula a * b = sum - a - b, there could be only one possible value for each of them. So, there's no need to continue the loop when you find them.
if(i*y === sum - i - y) {
answersArray.push([i,y]);
break;
}
I recommend looking at the problem in another way.
You are trying to find two numbers a and b using this formula a * b = sum - a - b.
Why not reduce the formula like this:
a * b + a = sum - b
a ( b + 1 ) = sum - b
a = (sum - b) / ( b + 1 )
Then you only need one for loop that produces the value of b, check if (sum - b) is divisible by ( b + 1 ) and if the division produces a number that is less than n.
for(let i = 1; i <= n; i++) {
let eq1 = sum - i;
let eq2 = i + 1;
if (eq1 % eq2 === 0) {
let a = eq1 / eq2;
if (a < n && a != i) {
return [[a, b], [b, a]];
}
}
}
You can solve this in linear time with two pointers method (page 77 in the book).
In order to gain intuition towards a solution, let's start thinking about this part of your code:
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
...
You already figured out this is the part of your code that is slow. You are trying every combination of i and y, but what if you didn't have to try every single combination?
Let's take a small example to illustrate why you don't have to try every combination.
Suppose n == 10 so we have 1 2 3 4 5 6 7 8 9 10 where sum = 55.
Suppose the first combination we tried was 1*10.
Does it make sense to try 1*9 next? Of course not, since we know that 1*10 < 55-10-1 we know we have to increase our product, not decrease it.
So let's try 2*10. Well, 20 < 55-10-2 so we still have to increase.
3*10==30 < 55-3-10==42
4*10==40 < 55-4-10==41
But then 5*10==50 > 55-5-10==40. Now we know we have to decrease our product. We could either decrease 5 or we could decrease 10, but we already know that there is no solution if we decrease 5 (since we tried that in the previous step). So the only choice is to decrease 10.
5*9==45 > 55-5-9==41. Same thing again: we have to decrease 9.
5*8==40 < 55-5-8==42. And now we have to increase again...
You can think about the above example as having 2 pointers which are initialized to the beginning and end of the sequence. At every step we either
move the left pointer towards right
or move the right pointer towards left
In the beginning the difference between pointers is n-1. At every step the difference between pointers decreases by one. We can stop when the pointers cross each other (and say that no solution can be obtained if one was not found so far). So clearly we can not do more than n computations before arriving at a solution. This is what it means to say that the solution is linear with respect to n; no matter how large n grows, we never do more than n computations. Contrast this to your original solution, where we actually end up doing n^2 computations as n grows large.
Hassan is correct, here is a full solution:
function removeNb (n) {
var a = 1;
var d = 1;
// Calculate the sum of the numbers 1-n without anything removed
var S = 0.5 * n * (2*a + (d *(n-1)));
// For each possible value of b, calculate a if it exists.
var results = [];
for (let numB = a; numB <= n; numB++) {
let eq1 = S - numB;
let eq2 = numB + 1;
if (eq1 % eq2 === 0) {
let numA = eq1 / eq2;
if (numA < n && numA != numB) {
results.push([numA, numB]);
results.push([numB, numA]);
}
}
}
return results;
}
In case it's of interest, CY Aries pointed this out:
ab + a + b = n(n + 1)/2
add 1 to both sides
ab + a + b + 1 = (n^2 + n + 2) / 2
(a + 1)(b + 1) = (n^2 + n + 2) / 2
so we're looking for factors of (n^2 + n + 2) / 2 and have some indication about the least size of the factor. This doesn't necessarily imply a great improvement in complexity for the actual search but still it's kind of cool.
This is part comment, part answer.
In engineering terms, the original function posted is using "brute force" to solve the problem, iterating every (or more than needed) possible combinations. The number of iterations is n is large - if you did all possible it would be
n * (n-1) = bazillio n
Less is More
So lets look at things that can be optimized, first some minor things, I'm a little confused about the first for loop and nArray:
// OP's code
for(let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
??? You don't really use nArray for anything? Length is just n .. am I so sleep deprived I'm missing something? And while you can sum a consecutive sequence of integers 1-n by using a for loop, there is a direct and easy way that avoids a loop:
sum = ( n + 1 ) * n * 0.5 ;
THE LOOPS
// OP's loops, not optimized
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
if(i != y) {
if(i*y === sum - i - y) {
Optimization Considerations:
I see you're on the right track in a way, cutting the starting i, y values in half since the factors . But you're iterating both of them in the same direction : UP. And also, the lower numbers look like they can go a little below half of n (perhaps not because the sequence start at 1, I haven't confirmed that, but it seems the case).
Plus we want to avoid division every time we start an instantiation of the loop (i.e set the variable once, and also we're going to change it). And finally, with the IF statements, i and y will never be equal to each other the way we're going to create the loops, so that's a conditional that can vanish.
But the more important thing is the direction of transversing the loops. The smaller factor low is probably going to be close to the lowest loop value (about half of n) and the larger factor hi is probably going to be near the value of n. If we has some solid math theory that said something like "hi will never be less than 0.75n" then we could make a couple mods to take advantage of that knowledge.
The way the loops are show below, they break and iterate before the hi and low loops meet.
Moreover, it doesn't matter which loop picks the lower or higher number, so we can use this to shorten the inner loop as number pairs are tested, making the loop smaller each time. We don't want to waste time checking the same pair of numbers more than once! The lower factor's loop will start a little below half of n and go up, and the higher factor's loop will start at n and go down.
// Code Fragment, more optimized:
let nHi = n;
let low = Math.trunc( n * 0.49 );
let sum = ( n + 1 ) * n * 0.5 ;
// While Loop for the outside (incrementing) loop
while( low < nHi ) {
// FOR loop for the inside decrementing loop
for(let hi = nHi; hi > low; hi--) {
// If we're higher than the sum, we exit, decrement.
if( hi * low + hi + low > sum ) {
continue;
}
// If we're equal, then we're DONE and we write to array.
else if( hi * low + hi + low === sum) {
answersArray.push([hi, low]);
low = nHi; // Note this is if we want to end once finding one pair
break; // If you want to find ALL pairs for large numbers then replace these low = nHi; with low++;
}
// And if not, we increment the low counter and restart the hi loop from the top.
else {
low++;
break;
}
} // close for
} // close while
Tutorial:
So we set the few variables. Note that low is set slightly less than half of n, as larger numbers look like they could be a few points less. Also, we don't round, we truncate, which is essentially "always rounding down", and is slightly better for performance, (though it dosenit matter in this instance with just the single assignment).
The while loop starts at the lowest value and increments, potentially all the way up to n-1. The hi FOR loop starts at n (copied to nHi), and then decrements until the factor are found OR it intercepts at low + 1.
The conditionals:
First IF: If we're higher than the sum, we exit, decrement, and continue at a lower value for the hi factor.
ELSE IF: If we are EQUAL, then we're done, and break for lunch. We set low = nHi so that when we break out of the FOR loop, we will also exit the WHILE loop.
ELSE: If we get here it's because we're less than the sum, so we need to increment the while loop and reset the hi FOR loop to start again from n (nHi).

In javascript, how do I add a random amount to a user's balance while controlling how much gets given total?

I'm trying to make it to where when a user does a certain thing, they get between 2 and 100 units. But for every 1,000 requests I want it to add up to 3,500 units given collectively.
Here's the code I have for adding different amounts randomly to a user:
if (Math.floor(Math.random() * 1000) + 1 === 900) {
//db call adding 100
}
else if (Math.floor(Math.random() * 100) + 1 === 90) {
//db call adding 40
}
else if (Math.floor(Math.random() * 30) + 1 === 20) {
//db call adding 10
}
else if (Math.floor(Math.random() * 5) + 1 === 4) {
//db call adding 5
}
else {
//db call adding 2
}
If my math is correct, this should average around 4,332 units per 1,000 calls. But obviously it would vary and I don't want that. I'd also like it to add random amounts instead, as the units added in my example are arbitrary.
EDIT: Guys, Gildor is right that I simply want to have 3,500 units, and give them away within 1,000 requests. It isn't even entirely necessary that it always reaches that maximum of 3,500 either (I could have specified that). The important thing is that I'm not giving users too much, while creating a chance for them to win a bigger amount.
Here's what I have set up now, and it's working well, and will work even better with some tweaking:
Outside of call:
var remaining = 150;
var count = 0;
Inside of call:
count += 1;
if (count === 100) {
remaining = 150;
count = 0;
}
if (Math.floor(Math.random() * 30) + 1 === 20) {
var addAmount = Math.floor(Math.random() * 85) + 15;
if (addAmount <= remaining) {
remaining -= addAmount;
//db call adding addAmount + 2
}
else {
//db call adding 2
}
}
else if (Math.floor(Math.random() * 5) + 1 === 4) {
var addAmount1 = Math.floor(Math.random() * 10) + 1;
if (addAmount1 <= remaining) {
remaining -= addAmount1;
//db call adding addAmount1 + 2
}
else {
//db call adding 2
}
}
else {
//db call adding 2
}
I guess I should have clarified, I want a "random" number with a high likelihood of being small. That's kind of part of the gimmick, where you have low probability of getting a larger amount.
As I've commented, 1,000 random numbers between 2 and 100 that add up to 3,500 is an average number of 3.5 which is not consistent with random choices between 2 and 100. You'd have to have nearly all 2 and 3 values in order to achieve that and, in fact couldn't have more than a couple large numbers. Nothing even close to random. So, for this to even be remotely random and feasible, you'd have to pick a total much large than 3,500. A random total of 1,000 numbers between 2 and 100 would be more like 51,000.
Furthermore, you can't dynamically generate each number in a truly random fashion and guarantee a particular total. The main way to guarantee that outcome is to pre-allocate random numbers that add up to the total that are known to achieve that and then random select each number from the pre-allocated scheme, then remove that from the choice for future selections.
You could also try to keep a running total and bias your randomness if you get skewed away form your total, but doing it that way, the last set of numbers may have to be not even close to random in order to hit your total consistently.
A scheme that could work if you reset the total to support what it should be for actual randomness (e.g. to 51,000) would be to preallocated an array of 500 random numbers between 2 and 100 and then add another 500 numbers that are the complements of those. This guarantees the 51 avg number. You can then select each number randomly from the pre-allocated array and then remove it form the array so it won't be selected again. I can add code to do this in a second.
function RandResults(low, high, qty) {
var results = new Array(qty);
var limit = qty/2;
var avg = (low + high) / 2;
for (var i = 0; i < limit; i++) {
results[i] = Math.floor((Math.random() * (high - low)) + low);
//
results[qty - i - 1] = (2 * avg) - results[i];
}
this.results = results;
}
RandResults.prototype.getRand = function() {
if (!this.results.length) {
throw new Error("getRand() called, but results are empty");
}
var randIndex = Math.floor(Math.random() * this.results.length);
var value = this.results[randIndex];
this.results.splice(randIndex, 1);
return value;
}
RandResults.prototype.getRemaining = function() {
return this.results.length;
}
var randObj = new RandResults(2, 100, 1000);
// get next single random value
if (randObj.getRemaining()) {
var randomValue = randObj.getRand();
}
Working demo for a truly random selection of numbers that add up to 51,000 (which is what 1,000 random values between 2 and 100 should add up to): http://jsfiddle.net/jfriend00/wga26n7p/
If what you want is the following: 1,000 numbers that add up to 3,500 and are selected from between the range 2 to 100 (inclusive) where most numbers will be 2 or 3, but occasionally something could be up to 100, then that's a different problem. I wouldn't really use the word random to describe it because it's a highly biased selection.
Here's a way to do that. It generates 1,000 random numbers between 2 and 100, keeping track of the total. Then, afterwards it corrects the random numbers to hit the right total by randomly selected values and decrementing them until the total is down to 3,500. You can see it work here: http://jsfiddle.net/jfriend00/m4ouonj4/
The main part of the code is this:
function RandResults(low, high, qty, total) {
var results = new Array(qty);
var runningTotal = 0, correction, index, trial;
for (var i = 0; i < qty; i++) {
runningTotal += results[i] = Math.floor((Math.random() * (high - low)) + low);
}
// now, correct to hit the total
if (runningTotal > total) {
correction = -1;
} else if (runningTotal < total) {
correction = 1;
}
// loop until we've hit the total
// randomly select a value to apply the correction to
while (runningTotal !== total) {
index = Math.floor(Math.random() * qty);
trial = results[index] + correction;
if (trial >= low && trial <= high) {
results[index] = trial;
runningTotal += correction;
}
}
this.results = results;
}
This meets an objective of a biased total of 3,500 and all numbers between 2 and 100, though the probability of a 2 in this scheme is very high and the probably of a 100 in this scheme is almost non-existent.
And, here's a weighted random generator that adds up to a precise total. This uses a cubic weighting scheme to favor the lower numbers (the probably of a number goes down with the cube of the number) and then after the random numbers are generated, a correction algorithm applies random corrections to the numbers to make the total come out exactly as specified. The code for a working demo is here: http://jsfiddle.net/jfriend00/g6mds8rr/
function RandResults(low, high, numPicks, total) {
var avg = total / numPicks;
var i, j;
// calculate probabilities for each value
// by trial and error, we found that a cubic weighting
// gives an approximately correct sub-total that can then
// be corrected to the exact total
var numBuckets = high - low + 1;
var item;
var probabilities = [];
for (i = 0; i < numBuckets; i++) {
item = low + i;
probabilities[i] = avg / (item * item * item);
}
// now using those probabilities, create a steps array
var sum = 0;
var steps = probabilities.map(function(item) {
sum += item;
return sum;
});
// now generate a random number and find what
// index it belongs to in the steps array
// and use that as our pick
var runningTotal = 0, rand;
var picks = [], pick, stepsLen = steps.length;
for (i = 0; i < numPicks; i++) {
rand = Math.random() * sum;
for (j = 0; j < stepsLen; j++) {
if (steps[j] >= rand) {
pick = j + low;
picks.push(pick);
runningTotal += pick;
break;
}
}
}
var correction;
// now run our correction algorithm to hit the total exactly
if (runningTotal > total) {
correction = -1;
} else if (runningTotal < total) {
correction = 1;
}
// loop until we've hit the total
// randomly select a value to apply the correction to
while (runningTotal !== total) {
index = Math.floor(Math.random() * numPicks);
trial = picks[index] + correction;
if (trial >= low && trial <= high) {
picks[index] = trial;
runningTotal += correction;
}
}
this.results = picks;
}
RandResults.prototype.getRand = function() {
if (!this.results.length) {
throw new Error("getRand() called, but results are empty");
}
return this.results.pop();
}
RandResults.prototype.getAllRand = function() {
if (!this.results.length) {
throw new Error("getAllRand() called, but results are empty");
}
var r = this.results;
this.results = [];
return r;
}
RandResults.prototype.getRemaining = function() {
return this.results.length;
}
As some comments pointed out... the numbers in the question does not quite make sense, but conceptually there are two approaches: calculate dynamically just in time or ahead of time.
To calculate just in time:
You can maintain a remaining variable which tracks how many of 3500 left. Each time when you randomly give some units, subtract the number from remaining until it goes to 0.
In addition, to make sure each time at least 2 units are given, you can start with remaining = 1500 and give random + 2 units each time.
To prevent cases that after 1000 gives there are still balances left, you may need to add some logic to give units more aggressively towards the last few times. However it will result in not-so-random results.
To calculate ahead of time:
Generate a random list with 1000 values in [2, 100] and sums up to 3500. Then shuffle the list. Each time you want to give some units, pick the next item in the array. After 1000 gives, generate another list in the same way. This way you get much better randomized results.
Be aware that both approaches requires some kind of shared state that needs to be handled carefully in a multi-threaded environment.
Hope the ideas help.

Get a random number focused on center

Is it possible to get a random number between 1-100 and keep the results mainly within the 40-60 range? I mean, it will go out of that range rarely, but I want it to be mainly within that range... Is it possible with JavaScript/jQuery?
Right now I'm just using the basic Math.random() * 100 + 1.
The simplest way would be to generate two random numbers from 0-50 and add them together.
This gives a distribution biased towards 50, in the same way rolling two dice biases towards 7.
In fact, by using a larger number of "dice" (as #Falco suggests), you can make a closer approximation to a bell-curve:
function weightedRandom(max, numDice) {
let num = 0;
for (let i = 0; i < numDice; i++) {
num += Math.random() * (max/numDice);
}
return num;
}
JSFiddle: http://jsfiddle.net/797qhcza/1/
You have some good answers here that give specific solutions; let me describe for you the general solution. The problem is:
I have a source of more-or-less uniformly distributed random numbers between 0 and 1.
I wish to produce a sequence of random numbers that follow a different distribution.
The general solution to this problem is to work out the quantile function of your desired distribution, and then apply the quantile function to the output of your uniform source.
The quantile function is the inverse of the integral of your desired distribution function. The distribution function is the function where the area under a portion of the curve is equal to the probability that the randomly-chosen item will be in that portion.
I give an example of how to do so here:
http://ericlippert.com/2012/02/21/generating-random-non-uniform-data/
The code in there is in C#, but the principles apply to any language; it should be straightforward to adapt the solution to JavaScript.
Taking arrays of numbers, etc. isn't efficient. You should take a mapping which takes a random number between 0 to 100 and maps to the distribution you need. So in your case, you could take f(x)=-(1/25)x2+4x to get a distribution with the most values in the middle of your range.
I might do something like setup a "chance" for the number to be allowed to go "out of bounds". In this example, a 20% chance the number will be 1-100, otherwise, 40-60:
$(function () {
$('button').click(function () {
var outOfBoundsChance = .2;
var num = 0;
if (Math.random() <= outOfBoundsChance) {
num = getRandomInt(1, 100);
} else {
num = getRandomInt(40, 60);
}
$('#out').text(num);
});
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>Generate</button>
<div id="out"></div>
fiddle: http://jsfiddle.net/kbv39s9w/
I needed to solve this problem a few years ago and my solution was easier than any of the other answers.
I generated 3 randoms between the bounds and averaged them. This pulls the result towards the centre but leaves it completely possible to reach the extremities.
It looks stupid but you can use rand twice:
var choice = Math.random() * 3;
var result;
if (choice < 2){
result = Math.random() * 20 + 40; //you have 2/3 chance to go there
}
else {
result = Math.random() * 100 + 1;
}
Sure it is possible. Make a random 1-100. If the number is <30 then generate number in range 1-100 if not generate in range 40-60.
There is a lot of different ways to generate such random numbers. One way to do it is to compute the sum of multiple uniformly random numbers. How many random numbers you sum and what their range is will determine how the final distribution will look.
The more numbers you sum up, the more it will be biased towards the center. Using the sum of 1 random number was already proposed in your question, but as you notice is not biased towards the center of the range. Other answers have propose using the sum of 2 random numbers or the sum of 3 random numbers.
You can get even more bias towards the center of the range by taking the sum of more random numbers. At the extreme you could take the sum of 99 random numbers which each were either 0 or 1. That would be a binomial distribution. (Binomial distributions can in some sense be seen as the discrete version of normal distributions). This can still in theory cover the full range, but it has so much bias towards the center that you should never expect to see it reach the endpoints.
This approach means you can tweak just how much bias you want.
What about using something like this:
var loops = 10;
var tries = 10;
var div = $("#results").html(random());
function random() {
var values = "";
for(var i=0; i < loops; i++) {
var numTries = tries;
do {
var num = Math.floor((Math.random() * 100) + 1);
numTries--;
}
while((num < 40 || num >60) && numTries > 1)
values += num + "<br/>";
}
return values;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="results"></div>
The way I've coded it allows you to set a couple of variables:
loops = number of results
tries = number of times the function will try to get a number between 40-60 before it stops running through the while loop
Added bonus: It uses do while!!! Awesomeness at its best
You can write a function that maps random values between [0, 1) to [1, 100] according to weight. Consider this example:
Here, the value 0.95 maps to value between [61, 100].
In fact we have .05 / .1 = 0.5, which, when mapped to [61, 100], yields 81.
Here is the function:
/*
* Function that returns a function that maps random number to value according to map of probability
*/
function createDistributionFunction(data) {
// cache data + some pre-calculations
var cache = [];
var i;
for (i = 0; i < data.length; i++) {
cache[i] = {};
cache[i].valueMin = data[i].values[0];
cache[i].valueMax = data[i].values[1];
cache[i].rangeMin = i === 0 ? 0 : cache[i - 1].rangeMax;
cache[i].rangeMax = cache[i].rangeMin + data[i].weight;
}
return function(random) {
var value;
for (i = 0; i < cache.length; i++) {
// this maps random number to the bracket and the value inside that bracket
if (cache[i].rangeMin <= random && random < cache[i].rangeMax) {
value = (random - cache[i].rangeMin) / (cache[i].rangeMax - cache[i].rangeMin);
value *= cache[i].valueMax - cache[i].valueMin + 1;
value += cache[i].valueMin;
return Math.floor(value);
}
}
};
}
/*
* Example usage
*/
var distributionFunction = createDistributionFunction([
{ weight: 0.1, values: [1, 40] },
{ weight: 0.8, values: [41, 60] },
{ weight: 0.1, values: [61, 100] }
]);
/*
* Test the example and draw results using Google charts API
*/
function testAndDrawResult() {
var counts = [];
var i;
var value;
// run the function in a loop and count the number of occurrences of each value
for (i = 0; i < 10000; i++) {
value = distributionFunction(Math.random());
counts[value] = (counts[value] || 0) + 1;
}
// convert results to datatable and display
var data = new google.visualization.DataTable();
data.addColumn("number", "Value");
data.addColumn("number", "Count");
for (value = 0; value < counts.length; value++) {
if (counts[value] !== undefined) {
data.addRow([value, counts[value]]);
}
}
var chart = new google.visualization.ColumnChart(document.getElementById("chart"));
chart.draw(data);
}
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(testAndDrawResult);
<script src="https://www.google.com/jsapi"></script>
<div id="chart"></div>
Here's a weighted solution at 3/4 40-60 and 1/4 outside that range.
function weighted() {
var w = 4;
// number 1 to w
var r = Math.floor(Math.random() * w) + 1;
if (r === 1) { // 1/w goes to outside 40-60
var n = Math.floor(Math.random() * 80) + 1;
if (n >= 40 && n <= 60) n += 40;
return n
}
// w-1/w goes to 40-60 range.
return Math.floor(Math.random() * 21) + 40;
}
function test() {
var counts = [];
for (var i = 0; i < 2000; i++) {
var n = weighted();
if (!counts[n]) counts[n] = 0;
counts[n] ++;
}
var output = document.getElementById('output');
var o = "";
for (var i = 1; i <= 100; i++) {
o += i + " - " + (counts[i] | 0) + "\n";
}
output.innerHTML = o;
}
test();
<pre id="output"></pre>
Ok, so I decided to add another answer because I felt like my last answer, as well as most answers here, use some sort of half-statistical way of obtaining a bell-curve type result return. The code I provide below works the same way as when you roll a dice. Therefore, it is hardest to get 1 or 99, but easiest to get 50.
var loops = 10; //Number of numbers generated
var min = 1,
max = 50;
var div = $("#results").html(random());
function random() {
var values = "";
for (var i = 0; i < loops; i++) {
var one = generate();
var two = generate();
var ans = one + two - 1;
var num = values += ans + "<br/>";
}
return values;
}
function generate() {
return Math.floor((Math.random() * (max - min + 1)) + min);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="results"></div>
I'd recommend using the beta distribution to generate a number between 0-1, then scale it up. It's quite flexible and can create many different shapes of distributions.
Here's a quick and dirty sampler:
rbeta = function(alpha, beta) {
var a = 0
for(var i = 0; i < alpha; i++)
a -= Math.log(Math.random())
var b = 0
for(var i = 0; i < beta; i++)
b -= Math.log(Math.random())
return Math.ceil(100 * a / (a+b))
}
var randNum;
// generate random number from 1-5
var freq = Math.floor(Math.random() * (6 - 1) + 1);
// focus on 40-60 if the number is odd (1,3, or 5)
// this should happen %60 of the time
if (freq % 2){
randNum = Math.floor(Math.random() * (60 - 40) + 40);
}
else {
randNum = Math.floor(Math.random() * (100 - 1) + 1);
}
The best solution targeting this very problem is the one proposed by BlueRaja - Danny Pflughoeft but I think a somewhat faster and more general solution is also worth mentioning.
When I have to generate random numbers (strings, coordinate pairs, etc.) satisfying the two requirements of
The result set is quite small. (not larger than 16K numbers)
The result set is discreet. (like integer numbers only)
I usually start by creating an array of numbers (strings, coordinate pairs, etc.) fulfilling the requirement (In your case: an array of numbers containing the more probable ones multiple times.), then choose a random item of that array. This way, you only have to call the expensive random function once per item.
Distribution
5% for [ 0,39]
90% for [40,59]
5% for [60,99]
Solution
var f = Math.random();
if (f < 0.05) return random(0,39);
else if (f < 0.95) return random(40,59);
else return random(60,99);
Generic Solution
random_choose([series(0,39),series(40,59),series(60,99)],[0.05,0.90,0.05]);
function random_choose (collections,probabilities)
{
var acc = 0.00;
var r1 = Math.random();
var r2 = Math.random();
for (var i = 0; i < probabilities.length; i++)
{
acc += probabilities[i];
if (r1 < acc)
return collections[i][Math.floor(r2*collections[i].length)];
}
return (-1);
}
function series(min,max)
{
var i = min; var s = [];
while (s[s.length-1] < max) s[s.length]=i++;
return s;
}
You can use a helper random number to whether generate random numbers in 40-60 or 1-100:
// 90% of random numbers should be between 40 to 60.
var weight_percentage = 90;
var focuse_on_center = ( (Math.random() * 100) < weight_percentage );
if(focuse_on_center)
{
// generate a random number within the 40-60 range.
alert (40 + Math.random() * 20 + 1);
}
else
{
// generate a random number within the 1-100 range.
alert (Math.random() * 100 + 1);
}
If you can use the gaussian function, use it. This function returns normal number with average 0 and sigma 1.
95% of this number are within average +/- 2*sigma. Your average = 50, and sigma = 5 so
randomNumber = 50 + 5*gaussian()
The best way to do that is generating a random number that is distributed equally in a certain set of numbers, and then apply a projection function to the set between 0 and a 100 where the projection is more likely to hit the numbers you want.
Typically the mathematical way of achieving this is plotting a probability function of the numbers you want. We could use the bell curve, but let's for the sake of easier calculation just work with a flipped parabola.
Let's make a parabola such that its roots are at 0 and 100 without skewing it. We get the following equation:
f(x) = -(x-0)(x-100) = -x * (x-100) = -x^2 + 100x
Now, all the area under the curve between 0 and 100 is representative of our first set where we want the numbers generated. There, the generation is completely random. So, all we need to do is find the bounds of our first set.
The lower bound is, of course, 0. The upper bound is the integral of our function at 100, which is
F(x) = -x^3/3 + 50x^2
F(100) = 500,000/3 = 166,666.66666 (let's just use 166,666, because rounding up would make the target out of bounds)
So we know that we need to generate a number somewhere between 0 and 166,666. Then, we simply need to take that number and project it to our second set, which is between 0 and 100.
We know that the random number we generated is some integral of our parabola with an input x between 0 and 100. That means that we simply have to assume that the random number is the result of F(x), and solve for x.
In this case, F(x) is a cubic equation, and in the form F(x) = ax^3 + bx^2 + cx + d = 0, the following statements are true:
a = -1/3
b = 50
c = 0
d = -1 * (your random number)
Solving this for x yields you the actual random number your are looking for, which is guaranteed to be in the [0, 100] range and a much higher likelihood to be close to the center than the edges.
This answer is really good. But I would like to post implementation instructions (I'm not into JavaScript, so I hope you will understand) for different situation.
Assume you have ranges and weights for every range:
ranges - [1, 20], [21, 40], [41, 60], [61, 100]
weights - {1, 2, 100, 5}
Initial Static Information, could be cached:
Sum of all weights (108 in sample)
Range selection boundaries. It basically this formula: Boundary[n] = Boundary[n - 1] + weigh[n - 1] and Boundary[0] = 0. Sample has Boundary = {0, 1, 3, 103, 108}
Number generation:
Generate random number N from range [0, Sum of all weights).
for (i = 0; i < size(Boundary) && N > Boundary[i + 1]; ++i)
Take ith range and generate random number in that range.
Additional note for performance optimizations. Ranges don't have to be ordered neither ascending nor descending order, so for faster range look-up range that has highest weight should go first and one with lowest weight should go last.

Can't figure out parseInt, even after research in JS and JQ [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have an object with a property containing a large string. This property has a value with random numbers generated earlier in the script in the format x , x , x , x ... (isn't and can't be an array because of other needs for the variable within the program) and so on. I am trying to get the sum of these numbers and my first thought was to use parseInt() to do this by splitting them all up then adding them together, but when I do this it only returns the first number. Is this what I should do but I'm just doing it wrong? Or is there another function that would make this easier?
The program is a blackjack game I'm making to see how well i understand everything I am learning.
Here is the function i am trying to make to see if the user busts when taking a hit (not much so far because i can't figure out the parseInt thing)
'
function checkBust() {
var total = parseInt(user.hand, 10);
}
'
the user object
'
var user = {
hand: dealUser()
};
'
and the functions to set the object property
function randomCard() {
// random number between 0 and 10
var j = Math.random() * 10;
// round that number into a var called card
var card = Math.round(j);
// if card is 0, assign a J Q or K by making a random number again
if (card === 0) {
//another random number
var k = Math.random() * 10;
// checks random number and assign J Q or K
if (k <= 4) {
card = 'J';
} else if (k <= 7) {
card = 'Q';
}
else {
card = 'K';
}
}
// value of the function is a single card
return card;
}
function dealUser() {
// empty array to store cards
var x = [];
// var to start for loop
var i = 0;
// start for loop
for (i; i < 2; i++) {
// add a random card to the i^th index of x
x[i] = randomCard();
}
// value for function is array of two cards x[0] , x[1]
var cards = x[0] + " , " + x[1];
return cards;
}
parseInt will stop parsing when it reaches a non numeric character.
parseInt('1234,5678', 10); // => 1234
// since a comma (,) is not a numeric character, everything after is ignored.
You have to split the string into an array of strings using the comma as the delimiter:
'1234,5678'.split(','); // => ['1234', '5678'];
Then parse each element of the array to convert them to numbers and then you can sum them.
Here's how I'd do it:
var nums = "1,2,3,4,5";
var sum = nums.split(',').reduce(function(memo, num) {
return memo + parseInt(num, 10);
}, 0);
console.log(sum); // => 15
That should work. See jsbin example.
Note the split parameter needs to match the delimiters you use in your string. for this example ',' is appropriate. For your example you might need /\s*,\s*/.
Unrelated
Since you provided an example of code I can see that you're spending a lot of effort attempting to duck punch and transform the values to the types you need instead of exposing the types in an object. Might I suggest:
function Stack(cards) {
this.cards = cards || [];
}
Stack.prototype.toString = function() {
return this.cards.join(' , ');
};
Stack.prototype.sum = function() {
return this.cards.reduce(function(memo, card) {
return memo + parseInt(card, 10);
}, 0);
};
function randomCard() {
return Math.floor(Math.random() * 13) + 1;
}
Stack.dealHand = function() {
var card1 = randomCard(), card2;
do { card2 = randomCard(); } while (card1 === card2);
return new Stack([card1, card2]);
};
// Example
var hand = Stack.dealHand();
console.log(hand + ' = ' + hand.sum()); // => '3 , 11 = 14'

Categories

Resources