My rand(0,1) php function returns me the 0 and 1 randomly when I call it.
Can I define something in php, so that it makes 30% numbers will be 0 and 70% numbers will be 1 for the random calls? Does php have any built in function for this?
Sure.
$rand = (float)rand()/(float)getrandmax();
if ($rand < 0.3)
$result = 0;
else
$result = 1;
You can deal with arbitrary results and weights, too.
$weights = array(0 => 0.3, 1 => 0.2, 2 => 0.5);
$rand = (float)rand()/(float)getrandmax();
foreach ($weights as $value => $weight) {
if ($rand < $weight) {
$result = $value;
break;
}
$rand -= $weight;
}
You can do something like this:
$rand = (rand(0,9) > 6 ? 1 : 0)
rand(0,9) will produce a random number between 0 and 9, and whenever that randomly generated number is greater than 6 (which should be nearly 70% time), it will give you 1 otherwise 0...
Obviously, it seems to be the easiest solution to me, but definitely, it wont give you 1 exactly 70% times, but should be quite near to do that, if done correctly.
But, I doubt that any solution based on rand will give you 1 exactly 70% times...
Generate a new random value between 1 and 100. If the value falls below 30, then use 0, and 1 otherwise:
$probability = rand(1, 100);
if ($probability < 30) {
echo 0;
} else {
echo 1;
}
To test this theory, consider the following loop:
$arr = array();
for ($i=0; $i < 10000; $i++) {
$rand = rand(0, 1);
$probability = rand(1, 100);
if ($probability < 30) {
$arr[] = 0;
} else {
$arr[] = 1;
}
}
$c = array_count_values($arr);
echo "0 = " . $c['0'] / 10000 * 100;
echo "1 = " . $c['1'] / 10000 * 100;
Output:
0 = 29.33
1 = 70.67
Create an array with 70% 1 and 30% 0s. Then random sort it. Then start picking numbers from the beginning of the array to the end :)
$num_array = array();
for($i = 0; $i < 3; $i++) $num_array[$i] = 0;
for($i = 0; $i < 7; $i++) $num_array[$i] = 1;
shuffle($num_array);
Pros:
You'll get exactly 30% 0 and 70% 1 for any such array.
Cons: Might take longer computation time than a rand() only solution to create the initial array.
I searched for an answer to my question and this was the topic I found.
But it didn't answered my question, so I had to figure it out myself, and I did :).
I figured out that maybe this will help someone else as well.
It's regarding what you asked, but for more usage.
Basically, I use it as a "power" calculator for a random generated item (let's say a weapon). The item has a "min power" and a "max power" value in the db. And I wanted to have 80% chances to have the "power" value closer to the lower 80% of the max possible power for the item, and 20% for the highest 20% possible max power (that are stored in the db).
So, to do this I did the following:
$min = 1; // this value is normally taken from the db
$max = 30; // this value is normally taken from the db
$total_possibilities = ($max - $min) + 1;
$rand = random_int(1, 100);
if ($rand <= 80) { // 80% chances
$new_max = $max - ($total_possibilities * 0.20); // remove 20% from the max value, so you can get a number only from the lowest 80%
$new_rand = random_int($min, $new_max);
} elseif ($rand <= 100) { // 20% chances
$new_min = $min + ($total_possibilities * 0.80); // add 80% for the min value, so you can get a number only from the highest 20%
$new_rand = random_int($new_min, $max);
}
echo $new_rand; // this will be the final item power
The only problem you can have, is if the initial $min and $max variables are the same (or obviously, if the $max is bigger than the $min). This will throw an error since the random works like this ($min, $max), not the other way around.
This code can be very easily changed to have more percentages for different purposes, instead of 80% and 20% to put 40%, 40% and 20% (or whatever you need). I think the code is pretty much easy to read and understand.
Sorry if this is not helpful, but I hope it is :).
It can't do any harm either way ;).
Related
In PHP I want to generate random numbers from 1 to 10 in a loop.
So for example:
$factor="1"; // this will be changed in another area
for ($i=0;$i<10;$i++) {
if ($factor=="1") {$output = rand(1,10);}
else if ($factor=="2") {$output = rand(1,10);}
else {$output = rand(1,10);}
}
Now to explain this - In result I want to receive 10 random numbers, but when $factor = "2", in that case I want to receive numbers from 6 to 10 more frequently as lower numbers.
It means, from 10 numbers I need to have 80% higher random numbers (it means larger than 5) and in 20% lower numbers (5 or lower).
E.g. 1,2,6,7,8,9,7,9,8,6 (1,2 are the only lower numbers = 20%, the rest are higher = 80)
If the $factor will change, then I want to change the percentage, in that case for example 40% lower numbers, 60% higher numbers.
The idea I have is to put each output in the loop to an array, then check each result and somehow calculate, if there is no 80% of larger numbers, then get random numbers again for those, but this seems to be an overkill.
Is there a simplier solution?
Let's go with the percentages you mention and first generate a random number between 1 and 100. Then the lower number, 1 to 20, have to represent outputs 1 to 5 and the higher numbers, 21 to 100, have to represent output 6 to 10. In PHP that would look like this:
function HighMoreOften()
{
$percentage = rand(1, 100);
if ($percentage <= 20) {
return rand(1, 5);
} else {
return rand(6, 10);
}
}
That should do the trick. You can also convert the percentage you got into the output, this would probably be slightly faster:
function HighMoreOften()
{
$percentage = rand(1, 100);
if ($percentage <= 20) {
return ceil($percentage / 5);
} else {
return 6 + ceil(($percentage - 20) / 20);
}
}
but personally I think the first version is easier to understand and change.
To change frequency you gonna need an array of numbers. And a sum to this direction. frequency is the relation of something between an array of things.
$t = 0;
// $factor = 1; // let's say that this means 20%
$factor = 2; // let's say that this means 40%
if ($factor === 1) {
for ($i = 1; $i <= 80; $i++) {
$t += rand(1,10);
}
for ($i = 1; $i <= 20; $i++) {
$t += rand(6,10);
}
} else if ($factor === 2) {
for ($i = 1; $i <= 60; $i++) {
$t += rand(1,10);
}
for ($i = 1; $i <= 40; $i++) {
$t += rand(6,10);
}
} else {
for ($i = 1; $i <= 100; $i++) {
$t += rand(1,10);
}
}
echo round(($t/100), 0);
Something like that! :)
I came with a very simple (maybe creepy) solution, but this works as I wanted:
echo print_r(generate("2"));
function generate($factor) {
$nums=array();
for ($i=0;$i<10;$i++) {
if ($i<$factor) {$rnd = rand(1,5);}
else {$rnd = rand(6,10);}
array_push($nums,$rnd);
}
return $nums;
}
I can also shuffle the final array results, as the lower numbers will be on the beginning always, but in my case it doesn't matter.
I need help getting a my probability odds closer to testing results with low percentages. What I have seems to work for percentages at 1% or higher but I need it to work with very low percentages such as 0.02% (down to 4 decimals). Anything below 1% tends to end up having around a 1% probability after running tests from running 1000-100000 tests at once the results are similar.
Example Results
ID Odds Test Total Test Odds
1 60.0000 301773 60.3546%
2 30.0000 148360 29.672%
3 9.9800 44897 8.9794%
4 0.0200 4970 0.994%
Function
// $values = [1,2,3,4]
// $weights = [60.0000,30.0000,9.9800,.0200]
private function getRandom($values, $weights)
{
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(0, array_sum($weights));
while($i < $count)
{
$n += $weights[$i];
if($n >= $num)
break;
$i++;
}
return $values[$i];
}
mt_rand returns an integer so comparing it to 0.02 is effectively the same as comparing it to 1. Hence you always get around 1% for the weights which are less than 1%. Try computing $num like this instead:
$num = mt_rand(0, array_sum($weights) * 100) / 100;
Demo on 3v4l.org
I've searched through a number of similar questions, but unfortunately I haven't been able to find an answer to this problem. I hope someone can point me in the right direction.
I need to come up with a PHP function which will produce a random number within a set range and mean. The range, in my case, will always be 1 to 100. The mean could be anything within the range.
For example...
r = f(x)
where...
r = the resulting random number
x = the mean
...running this function in a loop should produce random values where the average of the resulting values should be very close to x. (The more times we loop the closer we get to x)
Running the function in a loop, assuming x = 10, should produce a curve similar to this:
+
+ +
+ +
+ +
+ +
Where the curve starts at 1, peeks at 10, and ends at 100.
Unfortunately, I'm not well versed in statistics. Perhaps someone can help me word this problem correctly to find a solution?
interesting question. I'll sum it up:
We need a funcion f(x)
f returns an integer
if we run f a million times the average of the integer is x(or very close at least)
I am sure there are several approaches, but this uses the binomial distribution: http://en.wikipedia.org/wiki/Binomial_distribution
Here is the code:
function f($x){
$min = 0;
$max = 100;
$curve = 1.1;
$mean = $x;
$precision = 5; //higher is more precise but slower
$dist = array();
$lastval = $precision;
$belowsize = $mean-$min;
$abovesize = $max-$mean;
$belowfactor = pow(pow($curve,50),1/$belowsize);
$left = 0;
for($i = $min; $i< $mean; $i++){
$dist[$i] = round($lastval*$belowfactor);
$lastval = $lastval*$belowfactor;
$left += $dist[$i];
}
$dist[$mean] = round($lastval*$belowfactor);
$abovefactor = pow($left,1/$abovesize);
for($i = $mean+1; $i <= $max; $i++){
$dist[$i] = round($left-$left/$abovefactor);
$left = $left/$abovefactor;
}
$map = array();
foreach ($dist as $int => $quantity) {
for ($x = 0; $x < $quantity; $x++) {
$map[] = $int;
}
}
shuffle($map);
return current($map);
}
You can test it out like this(worked for me):
$results = array();
for($i = 0;$i<100;$i++){
$results[] = f(20);
}
$average = array_sum($results) / count($results);
echo $average;
It gives a distribution curve that looks like this:
I'm not sure if I got what you mean, even if I didn't this is still a pretty neat snippet:
<?php
function array_avg($array) { // Returns the average (mean) of the numbers in an array
return array_sum($array)/count($array);
}
function randomFromMean($x, $min = 1, $max = 100, $leniency = 3) {
/*
$x The number that you want to get close to
$min The minimum number in the range
$max Self-explanatory
$leniency How far off of $x can the result be
*/
$res = [mt_rand($min,$max)];
while (true) {
$res_avg = array_avg($res);
if ($res_avg >= ($x - $leniency) && $res_avg <= ($x + $leniency)) {
return $res;
break;
}
else if ($res_avg > $x && $res_avg < $max) {
array_push($res,mt_rand($min, $x));
}
else if ($res_avg > $min && $res_avg < $x) {
array_push($res, mt_rand($x,$max));
}
}
}
$res = randomFromMean(22); // This function returns an array of random numbers that have a mean close to the first param.
?>
If you then var_dump($res), You get something like this:
array (size=4)
0 => int 18
1 => int 54
2 => int 22
3 => int 4
EDIT: Using a low value for $leniency (like 1 or 2) will result in huge arrays, since testing, I recommend a leniency of around 3.
I need to generate x amount of random odd numbers, within a given range.
I know this can be achieved with simple looping, but I'm unsure which approach would be the best, and is there a better mathematical way of solving this.
EDIT: Also I cannot have the same number more than once.
Generate x integer values over half the range, and for each value double it and add 1.
ANSWERING REVISED QUESTION: 1) Generate a list of candidates in range, shuffle them, and then take the first x. Or 2) generate values as per my original recommendation, and reject and retry if the generated value is in the list of already generated values.
The first will work better if x is a substantial fraction of the range, the latter if x is small relative to the range.
ADDENDUM: Should have thought of this approach earlier, it's based on conditional probability. I don't know php (I came at this from the "random" tag), so I'll express it as pseudo-code:
generate(x, upper_limit)
loop with index i from upper_limit downto 1 by 2
p_value = x / floor((i + 1) / 2)
if rand <= p_value
include i in selected set
decrement x
return/exit if x <= 0
end if
end loop
end generate
x is the desired number of values to generate, upper_limit is the largest odd number in the range, and rand generates a uniformly distributed random number between zero and one. Basically, it steps through the candidate set of odd numbers and accepts or rejects each one based how many values you still need and how many candidates still remain.
I've tested this and it really works. It requires less intermediate storage than shuffling and fewer iterations than the original acceptance/rejection.
Generate a list of elements in the range, remove the element you want in your random series. Repeat x times.
Or you can generate an array with the odd numbers in the range, then do a shuffle
Generation is easy:
$range_array = array();
for( $i = 0; $i < $max_value; $i++){
$range_array[] .= $i*2 + 1;
}
Shuffle
shuffle( $range_array );
splice out the x first elements.
$result = array_slice( $range_array, 0, $x );
This is a complete solution.
function mt_rands($min_rand, $max_rand, $num_rand){
if(!is_integer($min_rand) or !is_integer($max_rand)){
return false;
}
if($min_rand >= $max_rand){
return false;
}
if(!is_integer($num_rand) or ($num_rand < 1)){
return false;
}
if($num_rand <= ($max_rand - $min_rand)){
return false;
}
$rands = array();
while(count($rands) < $num_rand){
$loops = 0;
do{
++$loops; // loop limiter, use it if you want to
$rand = mt_rand($min_rand, $max_rand);
}while(in_array($rand, $rands, true));
$rands[] = $rand;
}
return $rands;
}
// let's see how it went
var_export($rands = mt_rands(0, 50, 5));
Code is not tested. Just wrote it. Can be improved a bit but it's up to you.
This code generates 5 odd unique numbers in the interval [1, 20]. Change $min, $max and $n = 5 according to your needs.
<?php
function odd_filter($x)
{
if (($x % 2) == 1)
{
return true;
}
return false;
}
// seed with microseconds
function make_seed()
{
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
srand(make_seed());
$min = 1;
$max = 20;
//number of random numbers
$n = 5;
if (($max - $min + 1)/2 < $n)
{
print "iterval [$min, $max] is too short to generate $n odd numbers!\n";
exit(1);
}
$result = array();
for ($i = 0; $i < $n; ++$i)
{
$x = rand($min, $max);
//not exists in the hash and is odd
if(!isset($result{$x}) && odd_filter($x))
{
$result[$x] = 1;
}
else//new iteration needed
{
--$i;
}
}
$result = array_keys($result);
var_dump($result);
I have more than 200 entries in a database table and I would like to generate a random value for each entry, but in the end, the sum of entries values must equal 100. Is it possible to do this using a for loop and rand() in PHP?
You could simply normalize a set of numbers, like:
$numbers = array();
for ($i = 0; $i < 200; $i += 1) {
$numbers[] = rand();
}
$sum = array_sum($numbers);
// divide $sum by the target sum, to have an instant result, e.g.:
// $sum = array_sum($numbers) / 100;
// $sum = array_sum($numbers) / 42;
// ...
$numbers = array_map(function ($n) use($sum) {
return $n / $sum;
}, $numbers);
print_r($numbers);
print_r(array_sum($numbers)); // ~ 1
demo: http://codepad.viper-7.com/RDOIvX
The solution for your problem is to rand number from 0 to 200 then put in array, then sum the values and divide it by 200 after that. Loop through elements and divide every element by result of previous equatation it will give you the answer
$sum = 0;
$max = 100; //max value to be sumed
$nr_of_records = 200; // number of records that should sum to $max
$arr = array();
for($i=0;$i<$nr_of_records;++$i)
{
$arr[$i] = rand(0,$max);
}
$div = array_sum($arr) / $max;
for($i=0;$i<$nr_of_records;++$i)
{
$arr[$i] /= $div;
echo $arr[$i].'<br>';
}
echo array_sum($arr);
Created living example
How exact has the 100 to be? Just curious, because all hints end at using floating point values, which tend to be inacurate.
I'd propose using fractions... lets say 10000 fractions, each count 1/100 point (10000 * 1/100 = 100 points). Distribute 10000 points to 200 elements, using integers - and be absolutely sure, that the sum of all integers divided by 10000 is 100. There is no need for floats, just think around the corner...
Do a little over/under:
$size = 200;
$sum = 100;
$places = 3;
$base = round($sum/$size, $places);
$values = array_fill(0, $size, $base);
for($i=0; $i<$size; $i+=2) {
$diff = round((rand()/getrandmax()) * $base, $places);
$values[$i] += $diff;
$values[$i+1] -= $diff;
}
//optional: array_shuffle($values);
$sum = 0;
foreach($values as $item) {
printf("%0.3f ", $item);
$sum += $item;
}
echo $sum;
Output:
0.650 0.350 0.649 0.351 0.911 0.089 0.678 0.322 0.566 0.434 0.563 0.437 0.933 0.067 0.505 0.495 0.503 0.497 0.752 0.248 0.957 0.043 0.856 0.144 0.977 0.023 0.863 0.137 0.766 0.234 0.653 0.347 0.770 0.230 0.888 0.112 0.637 0.363 0.716 0.284 0.891 0.109 0.549 0.451 0.629 0.371 0.501 0.499 0.652 0.348 0.729 0.271 0.957 0.043 0.769 0.231 0.767 0.233 0.513 0.487 0.647 0.353 0.612 0.388 0.509 0.491 0.925 0.075 0.797 0.203 0.799 0.201 0.588 0.412 0.788 0.212 0.693 0.307 0.688 0.312 0.847 0.153 0.903 0.097 0.843 0.157 0.801 0.199 0.538 0.462 0.954 0.046 0.541 0.459 0.893 0.107 0.592 0.408 0.913 0.087 0.711 0.289 0.679 0.321 0.816 0.184 0.781 0.219 0.632 0.368 0.839 0.161 0.568 0.432 0.914 0.086 0.991 0.009 0.979 0.021 0.666 0.334 0.678 0.322 0.705 0.295 0.683 0.317 0.869 0.131 0.837 0.163 0.792 0.208 0.618 0.382 0.606 0.394 0.574 0.426 0.927 0.073 0.661 0.339 0.986 0.014 0.759 0.241 0.547 0.453 0.804 0.196 0.681 0.319 0.960 0.040 0.708 0.292 0.558 0.442 0.605 0.395 0.986 0.014 0.621 0.379 0.992 0.008 0.622 0.378 0.937 0.063 0.884 0.116 0.840 0.160 0.607 0.393 0.765 0.235 0.632 0.368 0.898 0.102 0.946 0.054 0.794 0.206 0.561 0.439 0.801 0.199 0.770 0.230 0.843 0.157 0.681 0.319 0.794 0.206 100
The rounding gets a bit squiffy if you're not using nice numbers like 100 and 200, but never more than 0.1 off.
Original question yesterday had exactly 200 entries and the sum "not greater than 100".
My original answer from yesterday:
Use random numbers not greater than 0.5 to be sure.
Alternatively, depending on how "random" those numbers need to be (how
much correlation is allowed), you could keep a running total, and if
it gets disproportionately high, you can mix in a bunch of smaller
values.
Edit:
Way to go changing the question, making me look stupid and get downvoted.
To get the exact sum you have to normalize, and better use exact fractions instead of floats to avoid rounding errors.