I'm making a small function in PHP that, like described in the title, need an array filled with random numbers inside a specified range, the numbers MUST repeat within it. As an example, filling an array with 20 random numbers between 1 and 10 should result in something like this:
Array = [2,5,8,2,8,5,3,9,6,3,4,6,3,1,2,1,2,3,7,1]
This code creates an array ($arr = array();) and then 20 times (the loop) pushes a random value on the end of the array.
The values are generated by random_int function that generates numbers in the given range (inclusively).
<?php
$arr = array();
for ($i = 0; $i < 20; $i++) {
array_push($arr, random_int(1, 10));
}
The source of randomness is quite good (depending on the system).
If you want such random number array generator as a function, do this:
<?php
function random_ints($count, $min, $max) {
$arr = array();
for ($i = 0; $i < $count; $i++) {
array_push($arr, random_int($min, $max));
}
}
Related
I know that rand(1,10); would generate a random number between 1 and 30.
How would I go about writing code that would pick a random number out of a group of numbers say 1,7,8 and 9?
Is it possible?
I am pretty sure rand is set up to only generate numbers within a range?
First thing is that rand(1,10) will generate a number between1 to 10 not 1 to 30.
PHP RAND FUNCTION
From a group of numbers, you can generate like this
$numbers = [1,7,8,9];
echo $numbers[rand(0,3)]
You can create custom array which you required, shuffle and get first value
$temp = [1,7,8,9];
shuffle($temp);
echo $temp[0];
shuffle — Shuffle an array
Demo
You could put your group of numbers into an array, and then use array_rand or shuffle to select one of them. For example:
$nums = array(1, 7, 8, 9);
$key = array_rand($nums);
echo $nums[$key] . PHP_EOL;
shuffle($nums);
echo $nums[0] . PHP_EOL;
Demo on 3v4l.org
Use this function (or inline code)
function SelectElementByRandom ( $list )
{
return count($list) ? $list[ rand(0,count($list)-1) ] : NULL;
}
The ?: operator ensures an result (NULL), even if the list is empty.
And for your example:
$result = SelectElementByRandom(array(1,7,8,9));
I would approach it like this. Create a loop that fills an array with random numbers in the range you're looking for (your group of numbers). After that, you choose a random index in that array, which is a random number in your group of numbers.
$min = 0;
$max = 10;
$group_size = 5;
$rand_group = array();
for($i = 0; $i < $group_size; $i++) {
$rand_group[$i] = rand($min,$max);
}
echo $rand_group[rand($min,$max)];
I have the following code to pick $n elements from an array $array in PHP:
shuffle($array);
$result = array_splice($array, 0, $n);
Given a large array but only a few elements (for example 5 out of 10000), this is relatively slow, so I would like to optimize it such that not all elements have to be shuffled. The values must be unique.
I'm looking fo the most performant alternative. We can assume that $array has no duplicates and is 0-indexed.
$randomArray = [];
while (count($randomArray) < 5) {
$randomKey = mt_rand(0, count($array)-1);
$randomArray[$randomKey] = $array[$randomKey];
}
This will provide exactly 5 elements with no duplicates and very quickly. The keys will be preserved.
Note: You'd have to make sure $array had 5 or more elements or add some sort of check to prevent an endless loop.
This function performs a shuffle on only $n elements where $n is the number of random elements you want to pick. It will also work on associative arrays and sparse arrays. $array is the array to work on and $n is the number of random elements to retrieve.
If we define the $max_index as count($array) - 1 - $iteration.
It works by generating a random number between 0 and $max_index. Picking the key at that index, and replacing its index with the value at $max_index so that it can never be picked again, as $max_index will be one less at the next iteration and unreachable.
In summary this is the Richard Durstenfeld's Fisher-Yates shuffle but operating only on $n elements instead of the entire array.
function rand_pluck($array, $n) {
$array_keys = array_keys($array);
$array_length = count($array_keys);
$max_index = $array_length -1;
$iterations = min($n, $array_length);
$random_array = array();
while($iterations--) {
$index = mt_rand(0, $max_index);
$value = $array_keys[$index];
$array_keys[$index] = $array_keys[$max_index];
array_push($random_array, $array[$value]);
$max_index--;
}
return $random_array;
}
The trick is to use a variation of shuffle or in other words a partial shuffle.
performance is not the only criterion, statistical efficiency, i.e unbiased sampling is as important (as the original shuffle solution is)
function random_pick( $a, $n )
{
$N = count($a);
$n = min($n, $N);
$picked = array_fill(0, $n, 0); $backup = array_fill(0, $n, 0);
// partially shuffle the array, and generate unbiased selection simultaneously
// this is a variation on fisher-yates-knuth shuffle
for ($i=0; $i<$n; $i++) // O(n) times
{
$selected = mt_rand( 0, --$N ); // unbiased sampling N * N-1 * N-2 * .. * N-n+1
$value = $a[ $selected ];
$a[ $selected ] = $a[ $N ];
$a[ $N ] = $value;
$backup[ $i ] = $selected;
$picked[ $i ] = $value;
}
// restore partially shuffled input array from backup
// optional step, if needed it can be ignored, e.g $a is passed by value, hence copied
for ($i=$n-1; $i>=0; $i--) // O(n) times
{
$selected = $backup[ $i ];
$value = $a[ $N ];
$a[ $N ] = $a[ $selected ];
$a[ $selected ] = $value;
$N++;
}
return $picked;
}
NOTE the algorithm is strictly O(n) in both time and space, produces unbiased selections (it is a partial unbiased shuffling) and produces output which is proper array with consecutive keys (not needing extra array_values etc..)
Use example:
$randomly_picked = random_pick($my_array, 5);
// or if an associative array is used
$randomly_picked_keys = random_pick(array_keys($my_array), 5);
$randomly_picked = array_intersect_key($my_array, array_flip($randomly_picked_keys));
For further variations and extensions of shuffling for PHP:
PHP - shuffle only part of an array
PHP shuffle with seed
How can I take n elements at random from a Perl array?
This will only show benifits for small n compared to an array shuffle, but you could
Choose a random index r n times, each time decreasing the limit by 1
Adjust for previously used indices
Take value
Store used index
Pseudocode
arr = []
used = []
for i = 0..n-1:
r = rand 0..len-i
d = 0
for j = 0..used.length-1:
if r >= used[j]:
d += 1
arr.append($array[r + d])
used.append(r)
return arr
You could generate n-times a random number with mt_rand() and then fill these values in a new array. To go against the case where the same index gets returned twice we use the actual returned index to fill the new array and check always if the index exists in the new array, if so we use while to loop through it as long as we get a duplicate index. At the end we use array_values() to get a 0-indexed array.
$count = count($array) - 1;
$new_array = array();
for($i = 0; $i < $n; $i++) {
$index = mt_rand(0, $count);
while(isset($new_array[$index])) {
$index = mt_rand(0, $count);
}
$new_array[$index] = $array[$index];
}
$new_array = array_values($new_array);
I wonder why everyone here make it so complicated?
Here's the fastest and simplest way:
$randomArray = array_rand(array_flip($array), $n);
I have an array of integers, where each integer value is a 'group membership' bitmask. For example, array(1, 4, 5) is interpreted as:
The first element (1) is a member of Group 0 (i.e. 2**0)
The second element (4) is a member of Group 2 (i.e. 2**2)
The third element (5) is a member of Groups 0 and 2 (i.e. 2**0 + 2**2)
So, my question is, how can I efficiently count the number of members in each of the groups?
My starting point would be to do something like this:
$count = array();
for ($i=0; $i<64; $i++) {
$count[$i] = 0;
$comparator = 2**$i;
foreach ($array as $value) {
if ($comparator & $value) $count[$i]++;
}
}
That seems like a long winded way to go about things. If I have 1,000 elements in the array, I am doing 64 X 1000 iterations.
Is there an easier way to go about this? Using an advanced array technique, for example, or a php extension for bitwise operations?
If you do not really need to keep 64 groups, and 62 is enough for you (or 30 on 32-bit platform), following code will help you:
$count = array();
$data = array(1, 4, 5, 7);
foreach ($data as $x)
for ($i=0, $j = 1; $j <= $x; $i++, $j <<= 1)
if($x & $j)
$count[$i]++;
print "count=";
print_r($count);
I have 2 arrays, both are multidimensional with same number of elements and same values, which are on different positions (those values are actually ID-s from my database, so one ID appears only once). How can I sort second array with values which are in first array?
For example - if first array looks like:
$array1[0][0] = 1;
$array1[0][x] = it doesn't matter what's here
$array1[1][0] = 4;
$array1[1][x] = it doesn't matter what's here
$array1[2][0] = 3;
$array1[2][x] = it doesn't matter what's here
...
how to sort second array so it would have same values as array1 on indexes [0][0], [1][0], [2][0], etc.
How I could solve problem is:
$i=0
while ($i < (count($array1)-2)){ // * check down
$find_id = $array1[$i][0];
// here I need to search for index of that ID in other array
$position = give_index($find_id, $array2);
// swapping positions
$temp = array2[$i][0];
$array2[$i][0] = $array2[$position][0];
$array2[$position][0] = $temp;
// increasing counter
i++;
}
function give_index($needle, $haystack){
for ($j = 0, $l = count($haystack); $j < $l; ++$j) {
if (in_array($needle, $haystack[$j][0])) return $j;
}
return false;
}
*There is only -2 because indexes start from 0 and also for the last element you don't need to check since it would be automatically sorted by last iteration of while-loop.
I don't find this solution good as I think that this is quite simple issue (maybe it's not even correct). Is there easier way in PHP that I'm missing?
This is the most efficient way I can think of:
function swap(&$a, &$b) {
$t = $a;
$a = $b;
$b = $t;
}
function find_index($id, $array, $from = 0) {
$index = false;
for ($i = $from, $c = count($array); $i < $c; $i++) {
if ($array[$i][0] == $id) {
$index = $i;
break;
}
}
return $index;
}
for ($i = 0, $c = count($array1); $i < ($c - 2); $i++) {
if ($array1[$i][0] != $array2[$i][0]) {
$fi = find_index($array1[$i][0], $array2, $i);
swap($array2[$i][0], $array2[$fi][0]);
}
}
What changes from yours?
I've defined a swap() function in order to swap any variable. That doesn't cost anything and makes everything look nicer. Also you can reuse that function later if you need to.
In the find_index (give_index in your code) we stop the loop once we find the correct index. Also we avoid the cost of an in_array function call.
We modified the find_index function to start only from the part of the array we haven't checked yet. Leading to a way more efficient way of scan the array.
In the for loop (a while loop was just wrong there) we stored the count of the array once, avoiding multiple calls.
Also we swap the $array2 values only if they are in the wrong place.
Other improvements
If you know anything else of the $array2 array you can make this even more performant. For example if you know that indexes are alternated like in $array1 you can change the main for loop from:
for ($i = 0, $c = count($array1); $i < ($c - 2); $i++) {
to
for ($i = 0, $c = count($array1); $i < ($c - 2); $i+2) {
(notice the $i+2 at the end) And you could do that in the find_index function as well.
Look into usort (http://php.net/manual/en/function.usort.php).
It provides a simple way to sort arrays using a user provided comparison function.
I need to generate a large list of random numbers from 600k to 2000k, but the
list can not have duplicates.
My current 'implementation' looks like this:
<?php
header('Content-type: text/plain');
$startTime = microtime(true);
$used = array();
for ($i=0; $i < 600000; ) {
$random = mt_rand();
//if (!in_array($random, $used)) {
$used[] = $random;
$i++;
//}
}
$endTime = microtime(true);
$runningTime = $endTime - $startTime;
echo 'Running Time: ' . $runningTime;
//print_r($used);
?>
If I keep the in_array test commented the processing time is around 1 second, so
the mt_rand calls and the used array filling are relatively 'cheap' but when I uncomment
the in_array test bad things happens! (I'm just waiting -it's been more then 10 minutes- for the script to terminate...)
So I'm looking for alternatives either on the duplicate detection side or in the generation part (How could i generate random numbers without the risk of getting duplicates)
I'm open to any suggestion.
For a quick/dirty solution, does using/checking array keys improve your speed at all?
$used = array();
for ($i = 0; $i < 600000; ) {
$random = mt_rand();
if (!isset($used[$random])) {
$used[$random] = $random;
$i++;
}
}
$used = array_values($used);
in_array requires to search the whole array in the worst case, that means linear costs (O(n)). But using the array key as – well – the key, the costs are constant (O(1)) since the costs for array access is always constant.
You could for example do something like this instead
$random = mt_rand();
$array = range($random, $random + 600000);
$array = shuffle($array);
That would create a array that first is in order, but then it shuffles the array, so the values will be random. No collisions! :D
If you do the looping anyways and if you don't need more than 600000 why would you check them at all, why not just append $i to $random. done. not random enough?
for ($i = 0; $i < 600000; $i++)
{
$yourArray[] = mt_rand() . $i;
}
Furthermore there is the array function array_unique, which removes duplicate values from an array.