Generating UNIQUE Random Numbers within a range - php
I need to generate random UNIQUE numbers within a range, how can I do that? I can generate random number by
generator:
$arr = [];
$x = rand($min, $max);
$len = count($arr);
$flag = 0;
for($i = 0; $i < $len; $i++)
{
if ($flag === 1)
goto generator;
if ($x === $arr[$i])
$flag = 1;
}
$arr[$index] = $x;
$index++;
goto generator;
I know this code is bad, so I need a better optimized code of my version !
help !
example:
if i need to generate 3 numbers within 1 to 15 they should be like 5, 9, 1 but not 3,1,2 [with in 1 - 3 (numbers i want to generate) ]
Array with range of numbers at random order:
$numbers = range(1, 20);
shuffle($numbers);
Wrapped function:
function UniqueRandomNumbersWithinRange($min, $max, $quantity) {
$numbers = range($min, $max);
shuffle($numbers);
return array_slice($numbers, 0, $quantity);
}
Example:
<?php
print_r( UniqueRandomNumbersWithinRange(0,25,5) );
?>
Result:
Array
(
[0] => 14
[1] => 16
[2] => 17
[3] => 20
[4] => 1
)
$len = 10; // total number of numbers
$min = 100; // minimum
$max = 999; // maximum
$range = []; // initialize array
foreach (range(0, $len - 1) as $i) {
while(in_array($num = mt_rand($min, $max), $range));
$range[] = $num;
}
print_r($range);
I was interested to see how the accepted answer stacks up against mine. It's useful to note, a hybrid of both may be advantageous; in fact a function that conditionally uses one or the other depending on certain values:
# The accepted answer
function randRange1($min, $max, $count)
{
$numbers = range($min, $max);
shuffle($numbers);
return array_slice($numbers, 0, $count);
}
# My answer
function randRange2($min, $max, $count)
{
$i = 0;
$range = array();
while ($i++ < $count) {
while(in_array($num = mt_rand($min, $max), $range));
$range[] = $num;
}
return $range;
}
echo 'randRange1: small range, high count' . PHP_EOL;
$time = microtime(true);
randRange1(0, 9999, 5000);
echo (microtime(true) - $time) . PHP_EOL . PHP_EOL;
echo 'randRange2: small range, high count' . PHP_EOL;
$time = microtime(true);
randRange2(0, 9999, 5000);
echo (microtime(true) - $time) . PHP_EOL . PHP_EOL;
echo 'randRange1: high range, small count' . PHP_EOL;
$time = microtime(true);
randRange1(0, 999999, 6);
echo (microtime(true) - $time) . PHP_EOL . PHP_EOL;
echo 'randRange2: high range, small count' . PHP_EOL;
$time = microtime(true);
randRange2(0, 999999, 6);
echo (microtime(true) - $time) . PHP_EOL . PHP_EOL;
The results:
randRange1: small range, high count
0.019910097122192
randRange2: small range, high count
1.5043621063232
randRange1: high range, small count
2.4722430706024
randRange2: high range, small count
0.0001051425933837
If you're using a smaller range and a higher count of returned values, the accepted answer is certainly optimal; however as I had expected, larger ranges and smaller counts will take much longer with the accepted answer, as it must store every possible value in range. You even run the risk of blowing PHP's memory cap. A hybrid that evaluates the ratio between range and count, and conditionally chooses the generator would be the best of both worlds.
The idea consists to use the keys, when a value is already present in the array keys, the array size stays the same:
function getDistinctRandomNumbers ($nb, $min, $max) {
if ($max - $min + 1 < $nb)
return false; // or throw an exception
$res = array();
do {
$res[mt_rand($min, $max)] = 1;
} while (count($res) !== $nb);
return array_keys($res);
}
Pro: This way avoids the use of in_array and doesn't generate a huge array. So, it is fast and preserves a lot of memory.
Cons: when the rate (range/quantity) decreases, the speed decreases too (but stays correct). For a same rate, relative speed increases with the range size.(*)
(*) I understand that fact since there are more free integers to select (in particular for the first steps), but if somebody has the mathematical formula that describes this behaviour, I am interested by, don't hesitate.
Conclusion: The best "general" function seems to be a mix between this function and #Anne function that is more efficient with a little rate. This function should switch between the two ways when a certain quantity is needed and a rate (range/quantity) is reached. So the complexity/time of the test to know that, must be taken in account.
If you want to generate 100 numbers that are random, but each number appearing only once, a good way would be to generate an array with the numbers in order, then shuffle it.
Something like this:
$arr = array();
for ($i=1;$i<=101;$i++) {
$arr[] = $i;
}
shuffle($arr);
print_r($arr);
Output will look something like this:
Array
(
[0] => 16
[1] => 93
[2] => 46
[3] => 55
[4] => 18
[5] => 63
[6] => 19
[7] => 91
[8] => 99
[9] => 14
[10] => 45
[11] => 68
[12] => 61
[13] => 86
[14] => 64
[15] => 17
[16] => 27
[17] => 35
[18] => 87
[19] => 10
[20] => 95
[21] => 43
[22] => 51
[23] => 92
[24] => 22
[25] => 58
[26] => 71
[27] => 13
[28] => 66
[29] => 53
[30] => 49
[31] => 78
[32] => 69
[33] => 1
[34] => 42
[35] => 47
[36] => 26
[37] => 76
[38] => 70
[39] => 100
[40] => 57
[41] => 2
[42] => 23
[43] => 15
[44] => 96
[45] => 48
[46] => 29
[47] => 81
[48] => 4
[49] => 33
[50] => 79
[51] => 84
[52] => 80
[53] => 101
[54] => 88
[55] => 90
[56] => 56
[57] => 62
[58] => 65
[59] => 38
[60] => 67
[61] => 74
[62] => 37
[63] => 60
[64] => 21
[65] => 89
[66] => 3
[67] => 32
[68] => 25
[69] => 52
[70] => 50
[71] => 20
[72] => 12
[73] => 7
[74] => 54
[75] => 36
[76] => 28
[77] => 97
[78] => 94
[79] => 41
[80] => 72
[81] => 40
[82] => 83
[83] => 30
[84] => 34
[85] => 39
[86] => 6
[87] => 98
[88] => 8
[89] => 24
[90] => 5
[91] => 11
[92] => 73
[93] => 44
[94] => 85
[95] => 82
[96] => 75
[97] => 31
[98] => 77
[99] => 9
[100] => 59
)
If you need 5 random numbers between 1 and 15, you should do:
var_dump(getRandomNumbers(1, 15, 5));
function getRandomNumbers($min, $max, $count)
{
if ($count > (($max - $min)+1))
{
return false;
}
$values = range($min, $max);
shuffle($values);
return array_slice($values,0, $count);
}
It will return false if you specify a count value larger then the possible range of numbers.
You can try next code:
function unique_randoms($min, $max, $count) {
$arr = array();
while(count($arr) < $count){
$tmp =mt_rand($min,$max);
if(!in_array($tmp, $arr)){
$arr[] = $tmp;
}
}
return $arr;
}
Get a random number. Is it stored in the array already? If not, store it. If so, then go get another random number and repeat.
When creating an application where I needed to generate 30,000 unique numbers within a larger range, I was able to cut down processing time from 25 seconds to 1.5 seconds using this method.
The idea is that PHP is much faster at generating random numbers than it is at checking the existence of items in an array - that is why using a while(in_array) loop can be slow.
$count = 0;
$collectNumbers = [];
while ($count < 30000) {
for ($i = 0; $i < 60000; $i++) {
$rand = mt_rand(1, 100000);
$collectNumbers[] = $rand;
}
$unique = array_unique($collectNumbers);
$count = count($unique);
}
$finalArray = array_slice($unique, 0, 30000);
This will return 30,000 unique numbers extremely quickly. Using an iteration value that is double the amount of numbers you need to generate can increase the likelihood of unique numbers the first iteration... but regardless of how many iterations it takes, this will produce a result faster than checking your array for repeated numbers in a loop.
This probably will solve your problem:
<?php print_r(array_rand(range(1,50), 5)); ?>
I guess this is probably a non issue for most but I tried to solve it. I think I have a pretty decent solution. In case anyone else stumbles upon this issue.
function randomNums($gen, $trim, $low, $high)
{
$results_to_gen = $gen;
$low_range = $low;
$high_range = $high;
$trim_results_to= $trim;
$items = array();
$results = range( 1, $results_to_gen);
$i = 1;
foreach($results as $result)
{
$result = mt_rand( $low_range, $high_range);
$items[] = $result;
}
$unique = array_unique( $items, SORT_NUMERIC);
$countem = count( $unique);
$unique_counted = $countem -$trim_results_to;
$sum = array_slice($unique, $unique_counted);
foreach ($sum as $key)
{
$output = $i++.' : '.$key.'<br>';
echo $output;
}
}
randomNums(1100, 1000 ,890000, 899999);
This is how I would do it.
$randnum1 = mt_rand(1,20);
$nomatch = 0;
while($nomatch == 0){
$randnum2 = mt_rand(1,20);
if($randnum2 != $randnum1){
$nomatch = 1;
}
}
$nomatch = 0;
while($nomatch == 0){
$randnum3 = mt_rand(1,20);
if(($randnum3 != $randnum1)and($randnum3 != $randnum2)){
$nomatch = 1;
}
}
Then you can echo the results to check
echo "Random numbers are " . $randnum1 . "," . $randnum2 . ", and " . $randnum3 . "\n";
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
Simply use this function and pass the count of number you want to generate
Code:
function randomFix($length)
{
$random= "";
srand((double)microtime()*1000000);
$data = "AbcDE123IJKLMN67QRSTUVWXYZ";
$data .= "aBCdefghijklmn123opq45rs67tuv89wxyz";
$data .= "0FGH45OP89";
for($i = 0; $i < $length; $i++)
{
$random .= substr($data, (rand()%(strlen($data))), 1);
}
return $random;}
The best way to generate the unique random number is
<?php
echo md5(uniqid(mt_rand(), true).microtime(true));
?>
Related
How to insert these element in array
Suppose an array is give: $given_array=Array( [0] => 30 [1] => 45 [2] => 60 [3] => 75 [4] => 90 [5] => 105 [6] => 120 [7] => 135) A number is given example: 195 Number until 195 needs to be inserted with a difference of 15 So resulting array is: Array( [0] => 30 [1] => 45 [2] => 60 [3] => 75 [4] => 90 [5] => 105 [6] => 120 [7] => 135 [8] => 150 [9] => 165 [10] => 180 [11] => 195) Need to know the best approach to do so time required is least. So far i have tried: $given_num=195; if((given_num-$given_array[count($given_array)-1])!=15 && count($given_array)>0){ while(($given_array[count($given_array)-1]+15)<=given_num){ $given_array[]=$given_array[count($given_array)-1]+15; } } Results are correct but not time feasible
I see that the goal is to have an array with subsequent numbers divisible by 15. Unless you really have to reuse the old array (I wouldn't know why), I would suggest creating a new array with range(): $given_array = range($given_array[0], 195, 15);
Haven't tried yours and not sure whats wrong with it regarding speed but try this. <?php $given_array=[30, 45, 60, 75, 90, 105, 120, 135]; $newNumber = 195; $count = floor(($newNumber - $given_array[count($given_array)-1]) / 15); for($i=0; $i<$count; $i++) { array_push($given_array, $given_array[count($given_array)-1]+15); } print_r($given_array); example: https://3v4l.org/vPiAW
I would say: $given_num = 195; $last = end($given_array); while ($given_num > $last) { $last = min($last + 15, $given_num); $given_array[] = $last; } But the range-version is quite nice =)
Another short solution using end function: while (($last = end($given_array)) < 195) $given_array[] = $last+15; // now, $given_array contains all the needed items
Loop through variables, increment by 10 and create new variable
I have $amounts eg. 31; 48; 57; 63; 79; 84 and 95 What I would like to do is loop through each “$amount”, and if they are above 50, create variable that adds 1 for each 10 increment Eg. $amount(57) = +1 $amount(63) = +2 $amount(79) = +3 $amount(84) = +4 $amount(95) = +5 UPDATED VERSION: Apologies for the vague question. I have $amount = array(end($percentage)); eg. 47, 63, 79, 95 What I would like to have is another variable to be created eg. $to_add if $amount > 50. Then for each $amount >= 50 add 1 to the $to_add Should look like: $amount(47) = NULL ($to_add = 0) $amount(50) = $to_add = 1 – *WOULD HAVE BEEN* $amount(63) = $to_add = 2 $amount(79) = $to_add = 3 $amount(80) = $to_add = 4 – *WOULD HAVE BEEN* $amount(95) = $to_add = 5 Thanks for the input thus far - I am testing the feedback I have already received - thank you very much!
This should work for you: (Here I just go through each element with array_map(), then I check if the value is over 50 and if yes I add 1 for every 10) <?php $amount = [31, 48, 57, 63, 79, 84, 95]; print_r($amount); $amount = array_map(function($v){ if($v / 50 >= 1) return ceil($v + ($v-50)/10); return $v; }, $amount); print_r($amount); ?> output: Array ( [0] => 31 [1] => 48 [2] => 57 [3] => 63 [4] => 79 [5] => 84 [6] => 95 ) Array ( [0] => 31 [1] => 48 [2] => 58 [3] => 65 [4] => 82 [5] => 88 [6] => 100 )
Somewhat like this: $array=array(); $i=0; foreach($amounts as $amount){ if($amount>50){ $value=floor($amount/10); $array[$i]=$value; $i++; } } var_dump($array); Now $array contains the values you want. You have to adapt the code to your code since I have no idea what $amount is(I assume values of an array)
PHP FFT module error
I wrote a FFT (Fast Fourier Transform) module for PHP recently. When I tried to test it, It always throws an error that the array $this->reverseTable has some index not defined. I got no clue of how to solve this problem. Here's the PHP code: <?php class FourierTransform { public $bufferSize; public $sampleRate; public $bandwidth; public $spectrum = array(); public $real = array(); public $imag = array(); public $peakBand = 0; public $peak = 0; public function __construct($bufferSize,$sampleRate){ $this->bufferSize = $bufferSize; $this->sampleRate = $sampleRate; $this->bandwidth = 2 / $bufferSize * $sampleRate / 2; } public function getBandFrequency($index){ return $this->bandwidth * $index + $this->bandwidth / 2; } public function calculateSpectrum(){ $bSi = 2 / $this->bufferSize; for($i = 0,$N = $this->bufferSize/2; $i < $N; $i++){ $rval = $this->real[$i]; $ival = $this->imag[$i]; $mag = $bSi * sqrt($rval * $rval + $ival * $ival); if($mag > $this->peak){ $this->peakBand = $i; $this->peak = $mag; } $this->spectrum[$i] = $mag; } } } class FFT extends FourierTransform { public $reverseTable = array(); public $sinTable = array(); public $cosTable = array(); public function __construct($bufferSize,$sampleRate){ parent::__construct($bufferSize,$sampleRate); $limit = 1; $bit = $bufferSize >> 1; while($limit < $bufferSize){ for($i = 0; $i < $limit; $i++){ $this->reverseTable[$i + $limit] = $this->reverseTable[$i] + $bit; } $limit = $limit << 1; $bit = $bit >> 1; } for($i = 0; $i < $bufferSize; $i++){ $this->sinTable[$i] = sin(-M_PI / $i); $this->cosTable[$i] = cos(-M_PI / $i); } } public function foward($buffer){ $k = floor(log($this->bufferSize,2)); if(pow(2,$k) !== $this->bufferSize) throw new Exception('Invalid buffer size, must be a power of 2.'); if($this->bufferSize !== count($buffer)) throw new Exception('Supplied buffer is not the same size as defined FFT.'); $halfSize = 1; for($i = 0; $i < $this->bufferSize; $i++){ $this->real[$i] = $buffer[$this->reverseTable[$i]]; $this->imag[$i] = 0; } while($halfSize < $this->bufferSize){ $phaseShiftReal = $this->cosTable[$halfSize]; $phaseShiftImag = $this->sinTable[$halfSize]; $currentPhaseShiftReal = 1; $currentPhaseShiftImag = 0; for($fftStep = 0; $fftStep < $halfSize; $fftStep++){ while($fftStep < $this->bufferSize){ $off = $fftStep + $halfSize; $tr = ($currentPhaseShiftReal * $this->real[$off]) - ($currentPhaseShiftImag * $this->imag[$off]); $ti = ($currentPhaseShiftReal * $this->imag[$off]) + ($currentPhaseShiftImag * $this->real[$off]); $this->real[$off] = $this->real[$fftStep] - $tr; $this->imag[$off] = $this->imag[$fftStep] - $ti; $this->real[$fftStep] += $tr; $this->imag[$fftStep] += $ti; $fftStep += $halfSize << 1; } $tmpReal = $currentPhaseShiftReal; $currentPhaseShiftReal = ($tmpReal * $phaseShiftReal) - ($currentPhaseShiftImag * $phaseShiftImag); $currentPhaseShiftImag = ($tmpReal * $phaseShiftImag) + ($currentPhaseShiftImag * $phaseShiftReal); } $halfSize = $halfSize << 1; } $this->calculateSpectrum(); } } ?> The test sample is a sine wave at 440Hz. When I tried to run the code, it throws this error Notice: Undefined offset: 0 in C:\Program Files (x86)\EasyPHP-12.1\www\fft.php on line 48 continuously. The array that has problem has data like this: Array ( [1] => 512 [2] => 256 [3] => 768 [4] => 128 [5] => 640 [6] => 384 [7] => 896 [8] => 64 [9] => 576 [10] => 320 [11] => 832 [12] => 192 [13] => 704 [14] => 448 [15] => 960 [16] => 32 [17] => 544 [18] => 288 [19] => 800 [20] => 160 [21] => 672 [22] => 416 [23] => 928 [24] => 96 [25] => 608 [26] => 352 [27] => 864 [28] => 224 [29] => 736 [30] => 480 [31] => 992 [32] => 16 [33] => 528 [34] => 272 [35] => 784 [36] => 144 [37] => 656 [38] => 400 [39] => 912 [40] => 80 [41] => 592 [42] => 336 [43] => 848 [44] => 208 [45] => 720 ... [978] => 303 [979] => 815 [980] => 175 [981] => 687 [982] => 431 [983] => 943 [984] => 111 [985] => 623 [986] => 367 [987] => 879 [988] => 239 [989] => 751 [990] => 495 [991] => 1007 [992] => 31 [993] => 543 [994] => 287 [995] => 799 [996] => 159 [997] => 671 [998] => 415 [999] => 927 [1000] => 95 [1001] => 607 [1002] => 351 [1003] => 863 [1004] => 223 [1005] => 735 [1006] => 479 [1007] => 991 [1008] => 63 [1009] => 575 [1010] => 319 [1011] => 831 [1012] => 191 [1013] => 703 [1014] => 447 [1015] => 959 [1016] => 127 [1017] => 639 [1018] => 383 [1019] => 895 [1020] => 255 [1021] => 767 [1022] => 511 [1023] => 1023 ) Edit: The previous problem is solved but now another problem is raised. At function forward() it throws an uncaught exception Invalid buffer size, must be a power of 2. Even if the buffer size is right. Any help will be appreciated.
Assuming that this is the whole class, I presume that the issue lies on this line inside your FFT constructor: $this->reverseTable[$i + $limit] = $this->reverseTable[$i] + $bit; From what I can tell you declare reverseTable as an array, but this line is the only place in the class where any elements are added to that array, so the fact that you're setting the reverseTable[$i+$limit] element using a never-defined reverseTable[$i] value is going to give you problems in the first first iteration of that while loop when it tries to use the undefined index $i (reverseTable[$i]). You'll have to give reverseTable[0] a value before you enter that loop.
Although you didn't provide where the line was, my guess is it has something to do with this line: $this->reverseTable[$i + $limit] = $this->reverseTable[$i] + $bit; This is in your constructor, and this bit especially looks wrong $this->reverseTable[$i] + $bit;. You are asking for the value from the reverseTable array, but this key is not initialized anywhere in the constructor. I am not sure how to fix this, as it is a logic error on your part.
Adjust function for pyramid-like distribution
In this question I got help to write a PHP function which gives a pyramid-like distribution: function getRandomStrength($min, $max) { $ln_low = log($min, M_E); $ln_high = log($max, M_E); $scale = $ln_high-$ln_low; $rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low; $value = round(pow(M_E, $rand), 1); return $value; } getRandomStrenth(1.1, 9.9); // output could be: 1.4 or 8.3 or 9.8 or 7.2 or 2.9 or ... When I run 50,000 iterations and check how often the numbers from 1 to 9 appear, I get the following list: 1 » 26% 2 » 19% 3 » 14% 4 » 10% 5 » 9% 6 » 7% 7 » 6% 8 » 6% 9 » 4% This is what I wanted to have. But now I would like to adjust this function a bit. The smaller values should appear more often and the big values should appear less often - so that I get a list like this: 1 » 28% 2 » 20% 3 » 15% 4 » 11% 5 » 9% 6 » 6% 7 » 5% 8 » 5% 9 » 2% As you can see, I just need a slight modification. But what can I change so that my function behaves as expected? I tried several things (e.g. changing the base of the logarithm) but this did not change anything.
You can use pow on the random number. $rand = pow( mt_rand()/mt_getrandmax(), 1.2 )*$scale+$ln_low; By playing with the exponent value, you can get less or more small value.
Reducing the $scale of your function by a small (constant) amount seems to generate results pretty close to what you're looking for. You can achieve more accurate results by making this reduction of $scale a function of the randomly generated number from mt_rand(), which would require saving (mt_rand()/mt_getrandmax()) to a variable and performing some additional math on $scale. Here are my tests, you can run it yourself: http://codepad.viper-7.com/ssblbQ function getRandomStrength($min, $max) { $ln_low = log($min, M_E); $ln_high = log($max, M_E); $scale = $ln_high-$ln_low - .05; // Subtract a small constant, vary between .05 and .08 $rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low; $value = round(pow(M_E, $rand), 1); return $value; } $values = array_fill(1, 9, 0); for( $i = 0; $i < 50000; $i++) { $values[ intval( getRandomStrength(1.1, 9.9)) ]++; } for( $i = 1; $i <= 9; $i++) { $values[ $i] /= 500; // / 50000 * 100 to get a percent } var_dump( $values); Output Run #1 - Constant = 0.5 array(9) { [1] => float(26.626) // Should be 28 [2] => float(19.464) // Should be 20 [3] => float(13.476) // Should be 15 [4] => float(10.41) // Should be 11 [5] => float(8.616) // Should be 9 [6] => float(7.198) // Should be 6 [7] => float(6.258) // Should be 5 [8] => float(5.52) // Should be 5 [9] => float(2.432) // Should be 2 } Run #2 - Constant = 0.65 array(9) { [1] => float(26.75) // Should be 28 [2] => float(19.466) // Should be 20 [3] => float(13.872) // Should be 15 [4] => float(10.562) // Should be 11 [5] => float(8.466) // Should be 9 [6] => float(7.222) // Should be 6 [7] => float(6.454) // Should be 5 [8] => float(5.554) // Should be 5 [9] => float(1.654) // Should be 2 } Run #3 - Constant = 0.70 array(9) { [1] => float(26.848) // Should be 28 [2] => float(19.476) // Should be 20 [3] => float(13.808) // Should be 15 [4] => float(10.764) // Should be 11 [5] => float(8.67) // Should be 9 [6] => float(7.148) // Should be 6 [7] => float(6.264) // Should be 5 [8] => float(5.576) // Should be 5 [9] => float(1.446) // Should be 2 }
For n in {0..1}, y=(x^n)-1, y will range from 0 to x-1. That curve is then easily mapped from 0 to some max value by multiplying by the range and dividing by (x-1). If you change the value x to something near one, the curve will be nearly linear, and at large values, the curve becomes more like a hockey-stick, but will still fall in the same range. My initial sample value of three won't be precisely what you expressed, but you can adjust it to get the distribution curve you're looking for. function getCustomStrength($min, $max, $x_val, $base) { $logmax = $base-1; $range = $max-$min; return (pow($base,$x_val)-1)*($range/($base-1))+$min; } function getRandomStrength($min, $max) { $rand = mt_rand()/mt_getrandmax(); $base = 3.0; return getCustomStrength($min, $max, $rand, $base); } getRandomStrength(1.1, 9.9);
Find missing numbers in array
I'm trying to find each missing number in an array like the following. Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 [6] => 7 [7] => 8 [8] => 9 [9] => 10 [10] => 11 [11] => 12 [12] => 13 [13] => 14 [14] => 15 [15] => 16 [16] => 17 [17] => 18 [18] => 19 [19] => 20 [20] => 21 [21] => 22 [22] => 23 [23] => 24 [24] => 25 [25] => 26 [26] => 27 [27] => 28 [28] => 29 [29] => 30 [30] => 31 [31] => 32 [32] => 33 [33] => 34 [34] => 35 [35] => 36 [36] => 37 [37] => 38 [38] => 39 [39] => 40 [40] => 41 [41] => 42 [42] => 43 [43] => 44 [44] => 45 [45] => 46 [46] => 47 [47] => 48 [48] => 49 [49] => 50 [50] => 51 [51] => 52 [52] => 53 [53] => 54 [54] => 55 [55] => 56 [56] => 57 [57] => 58 [58] => 59 [59] => 60 [60] => 61 [61] => 62 [62] => 63 [63] => 64 [64] => 67 [65] => 68 [66] => 69 ) The numbers 65,66 are missing in this particular array. My question how do I figure out which numbers are missing with the help of PHP. Specifically what I need to find out is the lowest missing number. Why: Because then I can assign that number to a member as an id.
You can make use of array_diff and range functions as: // given array. 3 and 6 are missing. $arr1 = array(1,2,4,5,7); // construct a new array:1,2....max(given array). $arr2 = range(1,max($arr1)); // use array_diff to get the missing elements $missing = array_diff($arr2,$arr1); // (3,6)
I'm assuming the number is the element, not the key, of the array. I'm also assuming that the numbers start from 1, not 0. $Expected = 1; foreach ($InputArray as $Key => $Number) { if ($Expected != $Number) { break; } $Expected++; } echo $Number;
For big sorted arrays of unique numbers, you can binary search the array for either the lowest or highest unused number. Cost=Log2N. Example: 65536 items can be searched in 16 loops since if ( arr[hi] - arr[lo] > hi - lo ) ... there are unused numbers in that range ... So (I don't know PHP, but it can be translated...): lo = first entry index hi = last entry index if ( arr[hi] - arr[lo] == hi - lo ) return arr[hi]+1; // no gaps so return highest + 1 do { mid = (lo + hi) / 2; if ( arr[mid] - arr[lo] > mid - lo ) // there is a gap in the bottom half somewhere hi = mid; // search the bottom half else lo = mid; // search the top half } while ( hi > lo + 1 ); // search until 2 left return arr[lo]+1;
If given input is not in sorted order and size of input is very large then we can use following logic in any programming language: Algorithm bring smaller chunk into memory from large input initialize three variables say min = 0, max = 0 and missingIds = [] scan smaller chunked input from left to right if scannedValue found in missingIds then, pop scannedValue from missingIds go to next value; If scanned value is near to min then, find all the missing numbers between scannedValue and min, push into missingIds min = scannedValue; Else if scanned value is near to max then, find all the missing numbers between scannedValue and max, push into missingIds max = scannedValue; repeat above steps until large input scanned from left to right Example in PHP <?php $largeInput = [40,41,42,43,44,45,1,2,3,4,5,6,7,8,9,10,11,12,13,14,35,36,37,38,39,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,67,68,69,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34]; $missingIds = []; $min = 0; $max = 0; $chunkSize = 10; $chunkNo = 0; $currentInput = array_slice($largeInput, $chunkNo, $chunkSize); while(count($currentInput) > 0) { foreach($currentInput as $id) { if(in_array($id,$missingIds)) { $missingIds = array_diff($missingIds,[$id]); continue; } if($id <= $min) { $distMin = $min - $id; if($distMin > 2) { $tempArr = range($id+1,$min-1); $missingIds = array_merge($missingIds, $tempArr); $tempArr = []; } else if ($distMin > 1) { $tempArr = [$id+1]; $missingIds = array_merge($missingIds, $tempArr); $tempArr = []; } $min = $id; } else if ($id >= $max){ $distMax = $id - $max; if($distMax > 2) { $tempArr = range($max+1,$id-1); $missingIds = array_merge($missingIds, $tempArr); $tempArr = []; } else if ($distMax > 1) { $tempArr = [$max+1]; $missingIds = array_merge($missingIds, $tempArr); $tempArr = []; } $max = $id; } } $chunkNo++; $currentInput = array_slice($largeInput, $chunkNo, $chunkSize); } print_r($missingIds);
//$idArrayMissing = array([0] => 1, [1] => 2, [2] => 4, [3] => 5, [4] => 6, [5] => 7); $idArrayMissing = array(1, 2, 4, 5, 6, 7); //$idArrayFull = array([0] => 1, [1] => 2, [2] => 3, [3] => 4, [4] => 5, [5] => 6); $idArrayFull = array(1, 2, 3, 4, 5, 6); function gap($arr) { while (list($k, $v) = each($arr)) if ($k != ($v-1)) return $k; return -1; } print "ok:" . gap($idArrayMissing) . "<br/>\n"; print "full:" . gap($idArrayFull) . "<br/>\n"; The return of the gap function can be 2 values: -1 could indicate that the array has been traversed and there are no free slots or $k+1 which could indicate that the first free slot is on the end of the array.
It can also be done easily by using in_array() function like this: // lets say $InputArray has all the data // lets declare a variable which we will search inside the $InputArray array and lets initialize it with either 0 or 1 or with the minimum value found inside $InputArray $start_counting = 1; $max_value = count($InputArray); if (!(in_array($start_counting, $InputArray))) { echo "Value: ".$start_counting." is missing!"."<br>" ; } else{ if($start_counting <= $max_value -1) {$start_counting++;} } else if($start_counting > $max_value -1) { echo "All missing numbers printed!" } }