Controlling likelyhood of randomly generated numbers - php

If I wanted a random number between one and three I could do $n = mt_rand(1,3).
There is a 33% chance that $n = 1, a 33% chance it's 2, and a 33% chance that it's 3.
What if I want to make it more difficult to get a 3 than a 1?
Say I want a 50% chance that a 1 is drawn, a 30% chance that a 2 is drawn and a 20% chance that a 3 is drawn?
I need a scalable solution as the possible range will vary between 1-3 and 1-100, but in general I'd like the lower numbers to be drawn more often than the higher ones.
How can I accomplish this?

There is a simple explanation of how you can use standard uniform random variable to produce random variable with a distribution similar to the one you want:
https://math.stackexchange.com/a/241543

This is maths.
In your example the just chose a random number between 0 and 99.
Values returned between 0 to 49 - call it 1
Values returned between 50 - 69 - Call it 2
Values returned between 70 - 99 - Call it 3
Simple if statement will do this or populate an array for the distribution required

Assuming a 1 - 10 scale, you can use a simple if statement and have the numbers represent percentages. And just have each if statement set $n to a specific. Only downfall, it isn't universal.
$dummy = mt_rand(1,10);
// represents 50%
if ($dummy <= 5) {
$n = 1;
}
// represents 40%
if ($dummy >= 6 && $dummy <= 9) {
$n = 2;
} else {
// represents 10%
$n = 3;
}

Related

Php random number generate

My task:
Generate random numbers between 1 and 20, to 1 decimal place.
However my issue as simple as mt_rand. I want most of the numbers generated to be lower around 0.5 - 4.5 with the occasional number being between 4.5-10 and very rarely say once every 12-20 hours being between 10-20.
I've been using the following but have no idea where to go from. I am a very basic self-taught programmer.
$min = 1;
$max = 20;
$suisse_interest = mt_rand ($min*10, $max*10) / 10
Maybe if I briefly explain why I want this it may help..
I own an online game and want to add 3 "banks" with each bank generating different interests each hour. Most of the time I want it low, but sometimes higher and very rarely very high (15-20%).
With the above code the random number goes too high to often.
Any help with this is greatly appreciated!
You need an exponential calculation. If you use a function similar to the following function, the probability for low numbers increases. Of course you need to adapt the numbers a bit to provide an output suiting your needs.
$i = 0;
while($i<30) {
$i++;
$rand = mt_rand(0, 7000) / 100; // 0.0 ~ 70.0
// This is the most important line:
$output = round( 20*(pow(0.95,$rand)) , 1);
echo "$output ";
}
Sample output:
1.8 4.3 2.6 5.5 3.7 15.5 1.6 0.6 0.6 1.6 5.8
1.3 6.1 3.2 0.8 1.7 14.7 7.9 1.3 10.3 5.5 12.6
1.5 8.4 1.5 0.9 13.3 5.8 7.5 1.7
As you see, mostly smaller number are printed.
The probability to get 20 is around 1.4% in my code whereas the probability to get a number smaller than 5 is around 78%
Try this.The probability to 1.0~4.5 is around 96%, 4.5~10.0 is around 2%, and 10.0~20.0 is around 2%.
<?php
// 1.0~4.5 96%
// 4.5~10.0 2%
// 10.0~20.0 2%
function fun() {
$num = mt_rand(1, 100);
if ($num > 0 && $num <= 96) {
$return = mt_rand(10, 45) / 10; // 96%
} else if ($num > 96 && $num <= 98) {
$return = mt_rand(45, 100) / 10; // 2%
} else {
$return = mt_rand(100, 200) / 10; // 2%
}
return sprintf("%01.1f",$return);
}
echo fun();
?>
This is not a PHP-specific problem.
What you need is a non-linear probability law, that you can then implement in PHP.
If you want something centered around an average value, the ideal would be a gaussian aka normal distribution, but computing it requires various complicated tricks, most of them being optimized for rapid generation at the cost of increasing complexity.
If you generate only a few values each hour, performance will not be an issue.
A reasonable approximation would be to sum 3 or 4 random variables, taking advantage of the central limit theorem.
Summing random values between 0 and twice your middle rate will create an approximation of a gaussian centered around your middle value.
You can then clamp values inferior to the middle point if you don't want low rates. The net result would be 50% chances of getting middle rate and a steadily decreasing chance to get up to twice that value.
An increasing number of sums will "narrow" the curve, making it less likely to get a high value.
for instance:
define ("INTEREST_MEAN", 10);
define ("INTEREST_SPREAD", 5);
function random_interest ()
{
$res = 0;
for ($i = 0 ; $i != INTEREST_SPREAD ; $i++) $res += mt_rand(0, 2*INTEREST_MEAN);
$res /= INTEREST_SPREAD; // normalize the sum to get a mean-centered result
$res = max ($res, INTEREST_MEAN); // clamp lower values
}

Highest multiplication with multiple and max number set

Not quite sure what to set this title as, or what to even search for. So I'll just ask the question and hope I don't get too many downvotes.
I'm trying to find the easiest way to find the highest possible number based on two fixed numbers.
For example:
The most I can multiply by is, say, 18 (first number). But not going over the resulted number, say 100 (second number).
2 x 18 = 36
5 x 18 = 90
But if the first number is a higher number, the second number would need to be less than 18, like so:
11 x 9 = 99
16 x 6 = 96
Here I would go with 11, because even though the second number is only 9, the outcome is the highest. The second number could be anything as long as it's 18 or lower. The first number can be anything, as long as the answer remains below 100. Get what I mean?
So my question is, how would write this in php without having to use switches, if/then statements, or a bunch of loops? Is there some math operator I don't know about that handles this sort of thing?
Thanks.
Edit:
The code that I use now is:
function doMath($cost, $max, $multiplier) {
do {
$temp = $cost * $multiplier;
if ($temp > $max) { --$multiplier; }
} while ($temp > $max);
return array($cost, $temp, $multiplier);
}
If we look at the 11 * 9 = 99 example,
$result = doMath(11, 100, 18);
Would return,
$cost = 11, $temp = 99, $multiplier = 9
Was hoping there was an easier way so that I wouldn't need to use a loop, being as how there are a lot of numbers I need to check.
If I understood you right, you are looking for the floor function, combining it with the min function.
Both a bigger number c and a smaller number a are part of the problem, and you want to find a number b in the range [0, m] such that a * b is maximal while staying smaller (strictly) than c.
In your example, 100/18 = 5.55555, so that means that 18*5 is smaller than 100, and 18*6 is bigger than 100.
Since floor gets you the integral part of a floating point number, $b = floor($c/$a) does what you want. When a divides c (that is, c/a is an integer already), you get a * b == c.
Now b may be outside of [0,m] so we want to take the smallest of b and m :
if b is bigger than m, we are limited by m,
and if m is bigger than b, we are limited by a * b <= c.
So in the end, your function should be :
function doMath($cost, $max, $multiplier)
{
$div = min($multiplier, floor($max/$cost));
return array($cost, $div * $cost, $div);
}

PHP Unique Random Numbers

What would be a good way to generate 7 unique random numbers between 1 and 10.
I can't have any duplicates.
I could write a chunk of PHP to do this (using rand() and pushing used numbers onto an array) but there must be a quick way to do it.
any advice would be great.
Create an array from 1 to 10 (range).
Put it in random order
(shuffle).
Select 7 items from the array (array_slice)
Populate an array with ten elements (the numbers one through ten), shuffle the array, and remove the first (or last) three elements.
Simple one-liner:
print_r(array_rand(array_fill(1, 10, true), 7));
Check out the comments in the php manual, there are several solutions for this.
An easy one is this one:
$min = 1;
$max = 10;
$total = 7;
$rand = array();
while (count($rand) < $total ) {
$r = mt_rand($min,$max);
if (!in_array($r,$rand)) $rand[] = $r;
}
Whole numbers? Well, if you want 7 out of 10 then you more efficiently DON'T want 3 out of 10.
Feel free to use any of the other responses but instead of creating 7 numbers start with 10 and eliminate 3. That will tend to speed things up by more than double.
The "shuffle" method has a MAJOR FALW. When the numbers are big, shuffle 3 billion indexs will instantly CAUSE 500 error. Here comes a best solution for really big numbers.
function getRandomNumbers($min, $max, $total) {
$temp_arr = array();
while(sizeof($temp_arr) < $total) $temp_arr[rand($min, $max)] = true;
return $temp_arr;
}
Say I want to get 10 unique random numbers from 1 billion to 4 billion.
$random_numbers = getRandomNumbers(1000000000,4000000000,10);
PS: Execution time: 0.027 microseconds

How do I find the ceiling and floor for a number from a set of numbers?

1st number: 50
2. 30
3. 70
4. 40
5. 11
and other number is 33
I need to calculate which two numbers the last number is between (using php) .. any help?
Iterate over your list and find the following two values:
The largest number that is smaller than your target number.
The smallest number that is larger than your target number.
In pseudo-code:
lowerlimit = Unknown
upperlimit = Unknown
for each n in list:
if (n <= target) and (lowerlimit is Unknown or n > lowerlimit):
lowerlimit = n
if (n >= target) and (upperlimit is Unknown or n < upperlimit):
upperlimit = n
Then lowerlimit and upperlimit are your answer. This algorithm requires O(n) time and O(1) extra space.
If you are going to test the same list with many different target numbers then it could make sense to sort the list first requiring O(n log(n)) time, but then you can find the limits in just O(log(n)) time using a binary search.
I'll not give you the code but give you some guidelines for your homework.
You need to do these steps to solve your problem.
Sort your list of numbers. (I guess you are storing them in an array so sort the array.)
With a for loop search for the place where the element N is bigger than your number and the element N-1 is smaller. That will give you your position.
Oh and to avoid really long loop. use "break" after you find your position.
Sorted List:
11
30
// your 33 is bigger than 30 and smaller than 40, so this is the position you want.
40
50
70
function isBetween($several_numbers, $number)
{
$return_numbers = array();
sort($several_numbers);
$j = 0;
//find the first number in the array that $number is bigger than
while($number > $several_numbers[$j]) $j++;
if ($j == 0 || $j > count($several_numbers) - 1) return array();
$return_numbers[0] = $several_numbers[$j-1];
while($number > $several_numbers[$j]) $j++;
if ($j > count($several_numbers)-1) return array();
$return_numbers[1] = $several_numbers[$j];
return $return_numbers;
}
print_r(isBetween(array(50, 30, 70, 40, 10), 33));
I don't know if I understood correctly but this seems to be it

Generate random player strengths in a pyramid structure (PHP)

For an online game (MMORPG) I want to create characters (players) with random strength values. The stronger the characters are, the less should exist of this sort.
Example:
12,000 strength 1 players
10,500 strength 2 players
8,500 strength 3 players
6,000 strength 4 players
3,000 strength 5 players
Actually, I need floating, progressive strength values from 1.1 to 9.9 but for this example it was easier to explain it with integer strengths.
Do you have an idea how I could code this in PHP? Of course, I would need mt_rand() to generate random numbers. But how can I achieve this pyramid structure?
What function is it? Root function, exponential function, power function or logarithm function?
Thanks in advance!
It should look like this in a graph:
Pyramid graph http://img7.imageshack.us/img7/107/pyramidy.jpg
You can simulate a distribution such as the one you described using a logarithmic function. The following will return a random strength value between 1.1 and 9.9:
function getRandomStrength()
{
$rand = mt_rand() / mt_getrandmax();
return round(pow(M_E, ($rand - 1.033) / -0.45), 1);
}
Distribution over 1000 runs (where S is the strength value floored):
S | Count
--+------
1 - 290
2 - 174
3 - 141
4 - 101
5 - 84
6 - 67
7 - 55
8 - 50
9 - 38
Note:
This answer was updated to include a strength value of 1.1 (which wasn't included before because of the rounding) and to fix the name of the mt_getrandmax() function
The simplest way to do this would be to provide 'bands' for where a random number should go. In your example, you have 15 players so you could have:
rand < 1/15, highest strength
1/15 < rand < 3/15, second highest
3/15 < rand < 6/15, third highest
6/15 < rand < 10/15, fourth highest
10/15 < rand < 15/15, lowest strength
You could also parameterise such a function with a 'max' number of each band that you allow and when the band is filled, it is subsumed into the next lowest existing band (apart from the bottom band, which would be subsumed into the next highest) to ensure only a certain number of each with a random distribution.
Edit adding from my comments:
To get a floating range pyramid structure the best function would most likely be a logarithm. The formula:
11 - log10(rand)
would work (with log10 being a logarithm with base 10) as this would give ranges like:
1 < rand < 10 = 9 < strength < 10
10 < rand < 100 = 8 < strength < 9
100 < rand < 1000 = 7 < strength < 8
etc.
but rand would need to range from 1 to 10^10 which would require a lot of randomness (more than most random generators can manage). To get a random number in this sort of range you could multiply some together. 3 random numbers could manage it:
11 - log10(rand1 * rand2 * rand3)
with rand1 having range 1-10000 and rand2 and rand3 having range 1-1000. This would skew the distribution away from a proper pyramid slightly though (more likely to have numbers in the centre I believe) so it may not be suitable.
workmad3 has the start of it down, I think, but there's a catch - you need to track your bucket sizes and whether or not they're full. A random number generator won't guarantee that. You'll need to assign your bucket values (strenghs) and sizes (number of people), and let your random generator tell you which bucket to drop the player into - if that one is full, 'overflow' to the next lower.
As to assigning the bucket sizes for a given strength value, that's the tricky bit (and I think what you're really working at). The characteristics of your desired distribution are critical. If you want a linear drop (which the pyramid shape hints at), a line of the form
strength = max_strength - m(number_characters)
would work. Varying the value of m would change the speed at which the line drops off, and will basically limit your max number of total characters. If you're looking for a more sophisticated way for the strength values to drop off, you could use a parabolic or hyperbolic curve - these are a bit more complex, but give you very different characteristics.
something like this
<?php
$rand = rand(1,10);
switch ($rand) {
case 1:
echo "band 1";
break;
case 2:
case 3:
echo "band 2";
break;
case 4:
case 5:
case 6:
echo "band 3";
break;
default:
echo "band 4";
break;
}
?>
Band 1 being the strongest, band 4 being the weakest.
Ofcourse this is basic, you would want to refactor this to use loops instead of hardcoded switches, but you get the idea :)
It's probably easiest to use percentages in this case.
From your examples would approximately be (converted to an array for ease of use later):
$strength[1] = .3; // start with a key of 1
$strength[2] = .26;
$strength[3] = .21;
$strength[4] = .15;
$strength[5] = .08;
That way, you can generate a random number using mt_rand() and divide by the maximum possible value to get a number between 0 and 1:
$rand = mt_rand() / mt_getrandmax(); // rand is some random value between 0 and 1
Then you can use a foreach statement to isolate each case:
$comparisonPercentage = 1;
$selectedLevel = count($strength); // covers the case where mt_rand() returns 0
foreach($strength as $level => $currentPercentage)
{
$comparisonPercentage -= $currentPercentage;
if ($rand > $comparisonPercentage)
{
$selectedLevel = $level;
break;
}
}
// $selectedLevel contains the level you need...
If you do it this way, you only have to change the $strength array if you need to fiddle with the percentages.
generate a random number between 0 and 40000, if its between 0 and 12000, assign strength 1, between 12000 and 22500 assign 2 etc.
Edit: for progressive values between 0 and 10 use the square root of a random number between 0 and 100, then substract if from 10
rand -> strengh
0-1 -> 9.9 -> 9 (1%)
2-4 -> 9 -> 8 (2%)
...
81 - 100 -> 1 - 0 (19%)
For results between 1.1 and 9.9 the formula would be in pseudocode)
strength = 10 - sqrt(rand(1..79))

Categories