The answers to this question say that unset() doesn't work, but weren't clear on what does. I have a recursive function, which uses static variables, however after the recursion has finished and the value has been returned, I need to reset those variables or subsequent calls (the recursive function is currently been called within a loop) would return wrong values.
In the linked question some people suggested trying $var = NULL outside the function which I did, but to seemingly no effect.
The reason why I used static variables and didn't just write them as parameters to the function is that I don't want a situation where the user can pass in arguments to that function, as the only arguments to the function should be supplied from within it.
My Code
<?
require_once("randX.php"); #"randX()" generates a random floating point number in a specified range.
// require_once("../displayArray.php");
error_reporting(E_ERROR | E_WARNING | E_PARSE);
/*
* Generates a valid, random probability distribution for a given array of elements, that can be used in conjunction with "probSelect()".
* Input:
$arr: An array of elements.
$control: A value that decides how much mass is allowed to be unilaterally dumped onto one element. A high value would permit distributions where most of the mass is concentrated on one element.
If an invalid value is provided, the default is used.
* Output: An associative array where the keys are the elements in the original array, and the values are their probabilities.
* #param array $arr: An array of elements for which the probability distribution would be generated.
* #param float $control: A variable which limits the inequality of the probability distribution.
*/
function probGen(array $arr, float $control = 0.01)
{
$control = ($control <= 1 && $control >= 0)?($control):(0.00001); #Use the default value if an invalid number is supplied.
static $result = []; #Initialises $result with an empty array on first function call.
static $max = 1; #Initialises $max with 1 on first function call.
foreach ($arr as $value)
{
$x = randX(0, $max); #Random probability value.
$result[$value] = ($result[$value] + $x)??0; #Initialise the array with 0 on first call, and on subsequent calls increment by $x to assign probability mass.
$max -= $x; #Ensures that the probability never sums to more than one.
}
/*
* After the execution of the above code, there would be some leftover probability mass.
* The code below adds it to a random element.
*/
$var = array_values($arr);
if($max <= $control) #To limit concentration of most of the probability mass in one variable.
{
$result[$var[rand(0,(count($var)-1))]] += $max; #Selects a random key and adds $max to it.
// print("<br>Sum: ".array_sum($result)."<br>");
return $result;
}
else
{
return probGen($arr, $control);
}
}
$max = NULL;
unset($max);
$result = NULL;
unset($result);
?>
This is always the problem with using static in any circumstances, I would change them to be parameters being passed in and having a default value...
function probGen(array $arr, float $control = 0.01, $result = [], $max = 1 )
(With the appropriate types).
These can then be passed down the chain in your further call...
return probGen($arr, $control, $result, $max);
This allows you better control over what these values start as (you can pass in your own values as a default) as well as being able to reset/adjust them as part of the call.
Related
Hi all I have the following function (With the internal stuff cut out) :
/**
* #param int $lastSyncTimeStampLocal
* #return void
*/
protected function compareData(int $lastSyncTimeStampLocal): void
{
$time_start = microtime(true);
foreach ($this->localData as $row) {
$key = array_search($row['uuid'], array_column($this->masterData, 'uuid'));
}
$time_end = microtime(true);
$execution_time = ($time_end - $time_start)/60;
}
This function is to compare 2 sets of data. The problem is the array_search is very slow with large amount of data.
eg if $this->localData and $this->masterData both contains 10000 records then this functions takes approximately 30 seconds to complete. I am wondering if there is an alternative/ any thing I can change to make this a lot faster?
Note uuid is a string like 'dkdue29u29dbiyedh92dye'
Thanks for any help
Difficult to test with large result sets, but I hope this works out.
First thing is not to repeat any operation in the loop if you can. So the array_column call is moved outside the loop.
Rather than using array_search(), using the values as the keys means you can just do an array reference. BUT using array_flip() on it's own would mean that you would end up with the last occurrence of rather than the first one. So this code uses array_reverse() followed by array_flip() to mean that you end up with a array keyed by the uuid with the value being the position in the array.
(May be worth printing out the $uuids array to see what I mean by this).
Then inside the loop, you just use a straight forward array access with $uuids[$row['uuid']] and use ?? -1 which sets -1 when the value is not present.
$uuids = array_flip(array_reverse(array_column($this->masterData, 'uuid')));
foreach ($this->localData as $row) {
$key = $uuids[$row['uuid']] ?? -1;
}
I have the following values in an array,
$values = array(
"1/4x1/4x1",
"1/2x1/2x1",
"3/4x3/4x1",
"1/4x1/4x2",
"1/2x1/2x2",
"3/4x3/4x2",
"1x1x1",
"1x2x1",
"2x1x1"
);
Considering the numbers in between 'x', I want the ascending order of the values as the following,
$values = array(
"1/4x1/4x1",
"1/4x1/4x2",
"1/2x1/2x1",
"1/2x1/2x2",
"3/4x3/4x1",
"3/4x3/4x2",
"1x1x1",
"1x2x1",
"2x1x1"
);
I'm new to PHP. Are there any specific functions for this? If not, please help with a way to find a solution. Thanks.
It is a bit complex, but you can avoid using eval:
// Transpose resulting 2x2 matrix using array_map behavior of
// tacking an arbitrary number of arrays and zipping elements, when null
// giving as a callback.
$evaluated = array_map(null, ...array_map(function ($factors) {
return array_map(function ($factor) {
// Explode fraction (into array with two elements)
// and destruct it into variables.
// `#` will silent the notice if not a fraction given (array with one
// element).
#list($dividend, $divisor) = explode('/', $factor); // **
// If divisor is given then calculate a fraction.
return $divisor
? floatval($dividend) / floatval($divisor)
: floatval($dividend);
}, explode('x', $factors));
}, $values));
// Assign values array by reference as a last element.
$evaluated[] =& $values;
// Unpack all arrays (three columns of fractions and given array) and pass
// them to `array_multisort` function, that will sort in turns by each of
// the columns.
array_multisort(...$evaluated);
print_r($values);
So, basically, we map each item from the array into an array of calculated fractions and then transpose this array to end up with three arrays representing columns. Then we pass this three array along with given array into array_multisort, that takes the given array by reference and reorders it as we want.
Here is the demo.
Don't use eval()! It is evil. And you can avoid it easily. You need to create a callback method for usort() that compares the dimensions (and if necessary converts fractions to numeric values).
I assume you always have 3 dimensions and they are either a positive integer or a fraction. I also assume that you are not sorting by volume but by dimension 1, dimension 2, dimension 3.
/**
* usort callback to sort dimensions
* #param {string} $a first comparable value
* #param {string} $b second comparable value
* #returns {int} 0, 1 or -1
*/
function sortByDimension($a, $b) {
$dimA = getNumericDimensions($a);
$dimB = getNumericDimensions($b);
// first dimension is the same: compare deeper
if ($dimA[0] == $dimB[0]) {
// second dimension is the same too: compare deeper
if ($dimA[1] == $dimB[1]) {
if ($dimA[2] == $dimB[2]) {
// value is the same: return 0
return 0;
}
// value A is larger: return 1, value B is larger: return -1
return ($dimA[2] > $dimB[2]) ? 1 : -1;
}
return ($dimA[1] > $dimB[1]) ? 1 : -1;
}
return ($dimA[0] > $dimB[0]) ? 1 : -1;
}
/**
* converts a string value to an array of numeric values
* #param {string} $val string of dimensions AxBxC
* #returns {array} an array with 3 dimensions and numeric values
*/
function getNumericDimensions($val) {
// split the value into the 3 dimensions
$dimensions = explode('x', $val);
// loop through the values and make them numeric
// note: the loop is per reference: we override the values!
foreach ($dimensions as &$dim) {
// check if it is a fraction
if (strpos($dim, '/') !== false) {
// split the fraction by the /
$fraction = explode('/', $dim);
// calculate a numeric value
$dim = $fraction[0] / $fraction[1];
} else {
// make it an integer
$dim = (int)$dim;
}
}
return $dimensions;
}
$values = array(
"1/4x1/4x1",
"1/2x1/2x1",
"3/4x3/4x1",
"1/4x1/4x2",
"1/2x1/2x2",
"3/4x3/4x2",
"1x1x1",
"1x2x1",
"2x1x1",
);
// sort the array (note: usort is per reference)
usort($values, 'sortByDimension');
print_r($values);
SO,
The problem
It's well known about pseudo-random numbers. 'Pseudo' actually means, that, despite they are random (i.e. unpredictable) in general, they still will be same in sequence, in which same generator init value was used. For example, in PHP there's mt_srand() function to do that. Example:
mt_srand(1);
var_dump(mt_rand(), mt_rand(), mt_rand());
-no matter, how many time we'll launch our script: generated three numbers will always be same in sequence.
Now, my issue is how to do the same - but for shuffling array. I.e. I want to create a function, which will accept input array to shuffle and seed. Within same seed value shuffling must have consecutive same order. I.e. let we call that function shuffleWithSeed() - and then following should work for every script launch:
$input = ['foo', 'bar', 'baz'];
$test = shuffleWithSeed($input, 1000);//1000 is just some constant value
var_dump($test); //let it be ['bar', 'foo', 'baz']
$test = shuffleWithSeed($test, 1000);
var_dump($test); //let it be ['baz', 'foo', 'bar']
$test = shuffleWithSeed($test, 1000);
var_dump($test); //let it be ['baz', 'bar', 'foo']
//...
-i.e. no matter how many times we'll do shuffle for our array - I want for the next script launch ordering sequence will be always the same within one seed value.
My approach
I have in mind this algorithm:
Initialize random numbers generator with passed seed
Generate N random numbers, where N is the number of $input members
Sort numbers from step 2
Make corresponding numbers be dependent from $input keys.
I've implemented this in:
function shuffleWithSeed(array $input, $seed=null)
{
if(!isset($seed))
{
shuffle($input);
return $input;
}
if(!is_int($seed))
{
throw new InvalidArgumentException('Invalid seed value');
}
mt_srand($seed);
$random = [];
foreach($input as $key=>$value)
{
$random[$key] = mt_rand();
}
asort($random);
$random = array_combine(array_keys($random), array_values($input));
ksort($random);
return $random;
}
-now, also found Fisher-Yates algorithm - but not sure if it can work with pseudorandom numbers (i.e. with seed)
The question
As you can see, I'm doing two sorts in my function - first by values and second by keys.
Can this be done with one sort? Or without sort at all? Input array could be large, so I want to avoid this.
However, may be my algorithm is not well? If yes, what other options could be suggested?
Here's a copy and paste of a function I have implemented a while ago for exactly this purpose:
/**
* Shuffles an array in a repeatable manner, if the same $seed is provided.
*
* #param array &$items The array to be shuffled.
* #param integer $seed The result of the shuffle will be the same for the same input ($items and $seed). If not given, uses the current time as seed.
* #return void
*/
protected function seeded_shuffle(array &$items, $seed = false) {
$items = array_values($items);
mt_srand($seed ? $seed : time());
for ($i = count($items) - 1; $i > 0; $i--) {
$j = mt_rand(0, $i);
list($items[$i], $items[$j]) = array($items[$j], $items[$i]);
}
}
It implements a simple Fisher-Yates shuffle with a seeded random number generator.
I'm trying to generate a unique randomized array with exceptions,
i got this far:
function rand_except($min, $max,$no_numbers, $except) {
//loop until you get a unique randomized array without except number
$end=false;
while (!$end){
$numbers = rand_array($min, $max,$no_numbers);//get unique randomized array
if(!in_array($except,$numbers)){
$end=true;
break;
}
}
return $numbers;
}
but now i want the function to loop until the except parameter isn't in the array
I suspect it would be easier to solve this problem by updating the rand_array function (or writing a modified version of it) to generate the array without the $except value to start with.
If that is not an option, here is one possible solution that doesn't involve calling the rand_array function over and over again:
$numbers = rand_array($min, $max-1, $no_numbers);
for ($i = 0; $i < count($numbers); $i++) {
if ($numbers[$i] >= $except) {
$numbers[$i] += 1;
}
}
You call the rand_array function but specify one less than the actual maximum number you want in the array. Then you loop over the results, and any value that is greater than or equal to the $except value, you increment by 1.
This is assuming that the $except value is in the range $max to $max. If not, you can just return rand_array($min, $max, $no_numbers); as is.
Does anyone know what's the randomness of PHP's shuffle() function? Does it depend on the operating system?
Does it use PHP's own seeder?
Is it possible to use mt_rand() as generator?
shuffle() function is based on the same generator as rand(), which is the system generator based on linear congruential algorithm. This is a fast generator, but with more or less randomness. Since PHP 4.2.0, the random generator is seeded automatically, but you can use srand() function to seed it if you want.
mtrand() is based on Mersenne Twister algorithm, which is one of the best pseudo-random algorithms available. To shuffle an array using that generator, you'd need to write you own shuffle function. You can look for example at Fisher-Yates algorithm. Writing you own shuffle function will yield to better randomness, but will be slower than the builtin shuffle function.
Update for PHP 7.1
Since the rng_fixes rfc was implemented for PHP 7.1, the implementation of shuffle now utilizes the Mersenne Twister PRNG (i.e. it uses mt_rand and is affected by calling mt_srand).
The legacy system PRNG (rand) is no longer available; the functions rand and srand are in fact aliased to their mt_ equivalents.
Based on Mirouf's answer (thank you so much for your contribution)... I refined it a little bit to take out redundant array counting. I also named the variables a little differently for my own understanding.
If you want to use this exactly like shuffle(), you could modify the parameter to be passed by reference, i.e. &$array, then make sure you change the return to simply: "return;" and assign the resulting random array back to $array as such:
$array = $randArr; (Before the return).
function mt_shuffle($array) {
$randArr = [];
$arrLength = count($array);
// while my array is not empty I select a random position
while (count($array)) {
//mt_rand returns a random number between two values
$randPos = mt_rand(0, --$arrLength);
$randArr[] = $array[$randPos];
/* If number of remaining elements in the array is the same as the
* random position, take out the item in that position,
* else use the negative offset.
* This will prevent array_splice removing the last item.
*/
array_splice($array, $randPos, ($randPos == $arrLength ? 1 : $randPos - $arrLength));
}
return $randArr;
}
It's random just like rand();
And as PHP style you don't need to seed
mt_rand()
Generates a random number.
shuffle()
Randomizes an array. It also generates new keys in the array rather than just rearranging the old ones.
If you want to seed in PHP you would have used mt_strand().
However, since PHP 4.2.0 seeding is done automatically in PHP when you call mt_rand.
Works with associative and numeric arrays:
function mt_shuffle_array($array) {
$shuffled_array = [];
$arr_length = count($array);
if($arr_length < 2) {
return $array;
}
while($arr_length) {
--$arr_length;
$rand_key = array_keys($array)[mt_rand(0, $arr_length)];
$shuffled_array[$rand_key] = $array[$rand_key];
unset($array[$rand_key]);
}
return $shuffled_array;
}
$array = [-2, -1, 'a' => '1', 'b' => '2', 'c' => '3', 11, 'd' => '4', 22];
$shuffled_array = mt_shuffle_array($array);
I've created a function who sort my array randomly.
/**
* Build a random array
*
* #param mixed $array
*
* #return array
*/
function random_array($array) {
$random_array = array();
// array start by index 0
$countArray = count($array) - 1;
// while my array is not empty I build a random value
while (count($array) != 0) {
//mt_rand return a random number between two value
$randomValue = mt_rand(0, $countArray);
$random_array[] = $array[$randomValue];
// If my count of my tab is 4 and mt_rand give me the last element,
// array_splice will not unset the last item
if(($randomValue + 1) == count($array)) {
array_splice($array, $randomValue, ($randomValue - $countArray + 1));
} else {
array_splice($array, $randomValue, ($randomValue - $countArray));
}
$countArray--;
}
return $random_array;
}
It's not the best way to do that but when I've used the function shuffle, it was always returning a random array in the same order. If this could help someone, I will be happy !