I need generate 4 different random numbers, with given range. Like this:
define('min', 3000);
define('range', 6000);
define('diff', 200);
$array[0] = rand(min ,min + range);
$array[1] = rand(min ,min + range);
while(abs($array[0]-$array[1])<diff)
$array[0] = rand(min ,min + range);
$array[2] = rand(min ,min + range);
while((abs($array[2]-$array[0])<diff)
||(abs($array[2]-$array[1])<diff))
$array[2] = rand(min ,min + range);
$array[3] =rand(min ,min + range);
while((abs($array[3]-$array[0])<diff)
||(abs($array[3]-$array[1])<diff)
||(abs($array[3]-$array[2])<diff))
$array[3] = rand(min ,min + range);
Is there any way to optimize this algorithm?
First, you might want to state that you want four random numbers within the same interval but with a minimum distance to each other (as the code does). Your current method involves looping, possibly indefinitely, but probably just for a while, until those numbers are reached.
So long as you only need four random numbers, it does not really matter much if you do it like this. It could be written more generically, allowing the natural extension of drawing more numbers than four, but that will not necessarily make it any faster, even for four numbers.
Assuming you want an upper bound on the time it takes (which your current code only gives with some probability), here is an algorithm that will work (so long as the interval is large enough): Draw a random number N and create two partitions of the interval, one for valid subsequent numbers lower than N and one for valid subsequent numbers higher than N. For each subsequent random number, pick an available partition at random and continue in the same fashion.
This runs in O($n) if you disregard the time count($intervals) takes (it could take O(1), but it gets a little uglier). I cannot say that this algorithm produces numbers with the same probability distribution as your initial algorithm. One thing that suggests that the probability distribution is biased: A partition is chosen at random with equal weight, not based on how large the interval is.
<?php
function random_interval($n, $min, $max, $distance) {
$result = array();
$intervals = array(array($min, $max));
while ($n-- > 0 && count($intervals) > 0) {
// find a random, valid interval
$i = array_rand($intervals);
// extract this interval
$cur_min = $intervals[$i][0];
$cur_max = $intervals[$i][1];
unset($intervals[$i]);
// find a random value within this interval
$r = rand($cur_min, $cur_max);
$result[] = $r;
// if there are valid intervals before and after $r,
// add these intervals back to the set of intervals
if ($r - $distance > $cur_min)
$intervals[] = array($cur_min, $r - $distance);
if ($r + $distance < $cur_max)
$intervals[] = array($r + $distance, $cur_max);
}
return $result;
}
When you generate the nth number, generate it within the range (min, min+range-2*diff*(n-1)), then iterate through the previous numbers, and if the currently generated number is "blocked" by the previous number, add 2*diff to it. Here is the code that does that, but also considers situations where a previously generated number is close to the bounds (in this case, you dont need to add 2*diff to the newly generated number, only a smaller amount):
define('min', 3000);
define('range', 6000);
define('diff', 200);
$numbers = array();
$bounds = array();
for($i = 0; $i<4; $i++){
$min = min;
$range = range;
foreach($bounds as $amount){
$range -= $amount;
}
$rnd = rand($min, $min + $range);
foreach($bounds as $position => $amount){
if($position <= $rnd){
$rnd += $amount;
}
else{
break;
}
}
$numbers[$i] = $rnd;
if($rnd <= min + diff){
$bounds['0'] = $rnd - min + diff;
}
elseif($rnd >= min + range - diff){
$bounds[strval($rnd-diff)] = min + range - $rnd + diff;
}
else{
$bounds[strval($rnd-diff)] = 2 * diff;
}
ksort($bounds);
}
print_r($numbers);
Its not to optimized, it runs in O(n^2log(n)), even thought O(nlog(n)) would be possible.
Related
For an endless number such as Pi, how would one go about finding the first occurrence of an exact sum of digits for a given number n.
For example. If n=20
Pi=3.14159265358979323846264338327950288419716939...
then the first occurrence is from digit 1 to digit 5 since:
1+4+1+5+9=20
if n=30, then the first occurrence is from digit 5 to digit 11
since 9+2+6+5+3+5=30
answer should have a working php demo
The answer to this is using sliding window that will maintain the sum. so maintain two pointers say i and j. Keep increasing j and adding the elements inside. when it crosses the desired sum increase i and decrease the element at i. Then keep increasing j until the sum is reached or the sum overflows so you repeat the above process.
Example sum = 30
141592653589793238 >> i=j=0 current_sum = 1
141592653589793238 >> i=0 j=6 current_sum=28
in the next iteration adding 5 will result in current_sum>30 so hence you increment i
141592653589793238 >> i=1 j=6 current_sum=27
141592653589793238 >> i=2 j=6 current_sum=23
141592653589793238 >> i=2 j=7 current_sum=28
Keep going in this manner and it will finally reach the window that is equal to the sum =30 . That should break you out of the loop and help you find the answer.
Method 1 (suggested by Ashwin Bhat)
This implementation uses two pivots. The sum of digits between $pivot_a and $pivot_b is computed. Depending on the value of the sum, we increment $pivot_b (if the sum is less) or $pivot_a (if the sum is greater). If the sum is equal to $n, break. The values of the pivots give the appropriate digit indices.
$pi = "314159265358979323846264338327950288419716939";
$n = 30;
$pivot_a = $pivot_b = 0;
$sum = 0;
for( ; $pivot_b < strlen($pi); ) {
if($sum < $n) {
$sum += $pi[$pivot_b++];
} elseif ($sum > $n) {
$sum -= $pi[$pivot_a++];
} else {
print('Solution found from digit '.$pivot_a.' to '.$pivot_b.'.');
exit;
}
}
print('No match was found.');
Method 2
This implementation uses one pivot only, from which it starts summing up the digits. If the sum happens to be greater than the desired value, it resets the sum to zero, shifts the pivot one position and starts the summing again.
$pi = "314159265358979323846264338327950288419716939";
$n = 30;
// Let's sum up all the elements from $pivot until we get the exact sum or a
// number greater than that. In the latter case, shift the $pivot one place.
$pivot = 0;
$sum = 0;
for($k=0 ; $sum != $n && $k < strlen($pi) ; $k++) {
$sum += $pi[$k];
print($pi[$k]);
if($sum > $n) {
print(' = '.$sum.' fail, k='.($pivot+1).PHP_EOL);
$sum = 0;
$k = $pivot++;
} elseif($sum < $n) {
print("+");
}
}
print(' = '.$n.' found from digit '.$pivot.' to '.$k.'.');
The implementation is not very effective but tries to explain the steps. It prints
3+1+4+1+5+9+2+6 = 31 fail, k=1
1+4+1+5+9+2+6+5 = 33 fail, k=2
4+1+5+9+2+6+5 = 32 fail, k=3
1+5+9+2+6+5+3 = 31 fail, k=4
5+9+2+6+5+3 = 30 found from digit 4 to 10.
Here's another approach. It builds an array of sums along the way and, on every iteration, attempts to add the current digit to the previous sums, and so on, while always only keeping the sums that are still relevant (< target).
The function either returns:
an array of 2 values representing the 0-based index interval within the digits,
or null if it couldn't find the target sum
Code:
function findFirstSumOccurrenceIndexes(string $digits, int $targetSum): ?array
{
$sums = [];
for ($pos = 0, $length = strlen($digits); $pos < $length; $pos++) {
$digit = (int)$digits[$pos];
if ($digit === $targetSum) {
return [$pos, $pos];
}
foreach ($sums as $startPos => $sum) {
if ($sum + $digit === $targetSum) {
return [$startPos, $pos];
}
if ($sum + $digit < $targetSum) {
$sums[$startPos] += $digit;
}
else {
unset($sums[$startPos]);
}
}
$sums[] = $digit;
}
return null;
}
Demo: https://3v4l.org/9t3vf
Apologies for the confusing title, I couldn't phrase it much differently.
I want to be able to generate a random range of x number(s) within the number 10000.
Such as 200-500 (201, 202, 203... ...499) or 9700 to 10000.
I also want the function to be easily changed so it will be a range of x-amount numbers. The above was a range of 200, x = 200... I would like to be able to easily change that.
Thank you.
Makes use of range():
$min = 1;
$max = 10000;
$nb = 300;
// in this example, $start can be from 1 to 9701
$start = mt_rand($min, $max - $nb + 1);
// in this example, $result can be from [1;300] to [9701;10000]
$result = range($start, $start + $nb - 1);
Try mt_rand()
echo mt_rand(0, 10000);
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);
How can I generate fix smaller random numbers from a large number. Addition of these smaller numbers must be equal to large number. Suppose I want to generate 400 random number and addition of these smaller number = e.g. 1,000,000. every number should be unique and have any value assign to it. Like Number 1=1000 and number 2 may contain only 5. But total of all the number must be a large number. Is there any algorithm to do this kind of operation in php?
function array_generate_sum($n, $total)
{
$sum = 0;
$arr = array();
for( ; $n >= 0; $n--)
{
$current = $n == 0 ? $total - $sum : mt_rand(1, $total - $sum - $n);
$sum += $current;
$arr[] = $current;
}
return $arr;
}
// Generate an array of 5 values whose sum is 30
array_generate_sum(5, 30);
I want to calculate Frequency (Monobits) test in PHP:
Description: The focus of the test is
the proportion of zeroes and ones for
the entire sequence. The purpose of
this test is to determine whether that
number of ones and zeros in a sequence
are approximately the same as would be
expected for a truly random sequence.
The test assesses the closeness of the
fraction of ones to ½, that is, the
number of ones and zeroes in a
sequence should be about the same.
I am wondering that do I really need to calculate the 0's and 1's (the bits) or is the following adequate:
$value = 0;
// Loop through all the bytes and sum them up.
for ($a = 0, $length = strlen((binary) $data); $a < $length; $a++)
$value += ord($data[$a]);
// The average should be 127.5.
return (float) $value/$length;
If the above is not the same, then how do I exactly calculate the 0's and 1's?
No, you really need to check all zeroes and ones. For example, take the following binary input:
01111111 01111101 01111110 01111010
. It is clearly (literally) one-sided(8 zeroes, 24 ones, correct result 24/32 = 3/4 = 0.75) and therefore not random. However, your test would compute 125.0 /255 which is close to ½.
Instead, count like this:
function one_proportion($binary) {
$oneCount = 0;
$len = strlen($binary);
for ($i = 0;$i < $len;$i++) {
$intv = ord($binary{$i});
for ($bitp = 0;$bitp < 7;$bitp++) {
$oneCount += ($intv>>$bitp) & 0x1;
}
}
return $oneCount / (8 * $len);
}