How to fix the php memory issue on a loop - php

I have a loop that generates all the possible combinations of bits by giving the number of bits desired, bu the issue is that I got out of memory when number of bits goes beyond 20, is there any optimizations that I can do, to solve this issue.
here my code :
function bitsGenerator($N)
{
$listN = $N;
$bits = ['0', '1'];
//check if input is valid or not
if (!is_int($listN)) {
echo 'Input must be numeric!';
}
if ($listN >= 1 && $listN <= 65) {
if ($listN == 1) {
echo '1';
exit;
}
for ($i = 1; $i <= ($listN - 1); $i++) {
$reverseBits = array_reverse($bits);
$prefixBit = preg_filter('/^/', '0', $bits);
$prefixReverseBits = preg_filter('/^/', '1', $reverseBits);
$bits = array_merge($prefixBit, $prefixReverseBits);
unset($prefixBit, $prefixReverseBits, $reverseBits);
}
$finalBits = array_slice($bits, -$listN);
foreach ($finalBits as $k => $v) {
echo $v . "\n";
}
} else {
echo 'Invalid input!';
}
}
The purpose of this function is to get the last $N combinations and display them all other combinations are thrown away, I'm looking for some kind of optimization to my code so that the $bits array will not store more than 65 item because the maximum number to bits thus the maximum number of combination to display is 65.
Thanks to every one for helping me.

The main problem is the size of the $bits array holding all your results. The array will contain 2^$N elements, each of $N length times 8 bits for each character (because you are using strings to have leading zeroes) so you'll end up with a memory consumption of approx (2^$N)*$N*8 which is 167772160 bytes. It won't get any smaller when using RAM.
Your working copies of $bits, preg_filter and array_merge will also consume a lot of RAM. Running your function with $N = 20 consumes 180375552 (172MiB).
BTW: unseting the variables will not reduce the consumed RAM because they would get overwritten in the next iteration anyway (or destroyed at the end of the function)
The following function was my first sketch based on your function and uses a bit less RAM: 171970560 bytes or 164MiB (vs 172MiB).
function myBitsGenerator($length)
{
if (!is_int($length)) {
die('Input must be numeric!');
}
if ($length < 1 || $length > 65) {
die('Input must be between 1 and 65');
}
$bitsArray = ['0', '1'];
$count = 2;
if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
for ($j = 0; $j < $count; $j++) {
$bitsArray[] = $bitsArray[$j] . '1';
$bitsArray[$j] = $bitsArray[$j] . '0';
}
$count += $j;
}
}
$printArray = array_slice($bitsArray, $count - $length);
array_walk(
$printArray,
function ($value) {
echo $value . PHP_EOL;
}
);
}
However, the generation of those numbers is too complicated and should be simplified:
Theoretical: A binary number can be written in decimal and vice versa. The number of possible combinations of binary numbers with the fixed length of $N are x = 2 ^ $N. Each decimal number from 0 to x represents a binary number in the results.
Practical example: (binary) (0b101 === 5) (int)
All you have to do is to pad the calculated binary number with zeroes.
The simplified generator looks like:
$n = pow(2, $length);
for ($i = 0; $i < $iterations; $i++) {
$binary = str_pad(decbin($i), $length, '0', STR_PAD_LEFT);
}
You can use this generator to generate
If you really need to use even less RAM you should think about storing it in a file, which makes the whole think slower but it will use way less RAM.
function fileBitsGenerator($length)
{
$handle = fopen('bits.txt', 'w+');
$iterations = pow(2, $length);
for ($i = 0; $i < $iterations; $i++) {
fwrite($handle, str_pad(decbin($i), $length, '0', STR_PAD_LEFT) . PHP_EOL);
}
}
This consumes just 2097152 bytes and scales!
But be aware that the performance will depend on your HDD/SSD speed and it executes a lot of write operations (which might shorten your SSD life span). For example: the resulting file bits.txt is 92MB big if length = 22

Related

PHP Memory Exhausted Error with simple fractions

I am using this library to work with fractions in PHP. This works fine but sometimes, I have to loop over a lot of values and this results in the following error:
Allowed memory size of 134217728 bytes exhausted
I can allocate more memory using PHP ini but that is a slippery slope. At some point, I am going to run out of memory when the loops are big enough.
Here is my current code:
for($q = 10; $q <= 20; $q++) {
for($r= 10; $r <= 20; $r++) {
for($p = 10; $p <= 20; $p++) {
for($s = 10; $s <= 20; $s++) {
for($x = 50; $x <= 100; $x++) {
for($y = 50; $y <= 100; $y++) {
$den = ($q + $r + 1000) - ($p + $s);
$num = $x + $y;
$c_diff = new Fraction($num, $den);
}
}
}
}
}
}
I used memory_get_peak_usage(true)/(1024*1024) to keep track of the memory the script is using. The total memory used was just 2MB until I added the line that creates a new fraction.
Could anyone please guide me on how to get rid of this error. I went through the code of the library posted on GitHub here but can't figure out how to get rid of the exhausted memory error. Is this because of the static keyword? I am beginner so I am not entirely sure what's going on.
The library code is about a 100 lines after removing the empty lines and comments. Any help would be highly appreciated.
UPDATE:
The script exhausts its memory even if I use just this block of code and nothing else. I definitely know that creating a new Fraction object is the cause of exhausting memory.
I thought that there was not need to unset() anything because the same one variable to store the new fractional value over and over again.
This leads me to think that whenever I creating a new Fraction object something else happens which in the library code that takes up memory which is not released on rewriting the value in the $c_diff variable.
I am not very good at this so I thought it has something to do with the static keyword used at a couple of places. Could anyone confirm it for me?
If this issue can indeed be resolved by using unset(), should I place it at the end of the loop?
Various Possible fixes and efficiencies:
You have 6 for loops, each loop cycles a single integer value within various ranges.
But your calculation only uses 3 values and so it doesn't matter if $p = 10; $s = 14; or $p = 13; $s = 11; These are entirely equivilant in the calculation.
All you need is the sum; so once you've found that the value 24 works; you can find all the parts (over the minimum value of 10) that fit that value: ie (24 (sum) - 10 (min) = 14), then collect the values within the range; so there are 10,14, 11,13 , 12,12, 13,11, 14,10 valid values. savng yourself 80%+ processing work on the inner for loops.
$pairs = "p,s<BR>"; //the set of paired values found
$other = $sum - $min;
if($other > $max){
$other = $sum - $max;
}
$hardMin = $min;
while ($other >= $hardMin && $min >= $hardMin && $min <= $max){
$pairs .= $min.", ".$other."<BR>";
$other--; // -1
$min++; // +1
}
print $pairs;
Giving:
  p,s
10,14
11,13
12,12
13,11
14,10
So for this for loop already, you may only need to do ~10% of the total work cycling the inner loops.
Stop instantiating new classes. Creating a class is expensive. Instad you create one class and simply plug the values in:
Example:
$c_diff = new Fraction();
for(...){
for(...){
$c_diff->checkValuesOrWhateverMethod($num, $den)
}
}
This will save you significant overhead (depending on the structure of the class)
The code you linked on GitHub is simply to turn the value into a fraction and seems to be highly inefficient.
All you need is this:
function float2frac($n, $tolerance = 1.e-6) {
$h1=1; $h2=0;
$k1=0; $k2=1;
$b = 1/$n;
do {
$b = 1/$b;
$a = floor($b);
$aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
$aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
$b = $b-$a;
} while (abs($n-$h1/$k1) > $n*$tolerance);
return $h1."/".$k1;
}
Taken from this excellent answer.
Example:
for(...){
for(...){
$den = ($q + $r + 1000) - ($p + $s);
$num = $x + $y;
$value = $num/den;
$c_diff = float2frac($value);
unset($value,den,$num);
}
}
If you need more precision you can read this question and update PHP.ini as appropriate, but personally I would recommend you use more specialist maths languages such as Matlab or Haskell.
Putting it all together:
You want to check three values, and then find the equivilant part of each one.
You want to simply find the lowest common denominator fraction (I think).
So:
/***
* to generate a fraction with Lowest Common Denominator
***/
function float2frac($n, $tolerance = 1.e-6) {
$h1=1; $h2=0;
$k1=0; $k2=1;
$b = 1/$n;
do {
$b = 1/$b;
$a = floor($b);
$aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
$aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
$b = $b-$a;
} while (abs($n-$h1/$k1) > $n*$tolerance);
return $h1."/".$k1;
}
/***
* To find equivilants
***/
function find_equivs($sum = 1, $min = 1, $max = 2){
$value_A = $sum - $min;
$value_B = $min;
if($value_A > $max){
$value_B = $sum - $max;
$value_A = $max;
}
$output = "";
while ($value_A >= $min && $value_B <= $max){
if($value_A + $value_B == $sum){
$output .= $value_A . ", " . $value_B . "<BR>";
}
$value_A--; // -1
$value_B++; // +1
}
return $output;
}
/***
* Script...
***/
$c_diff = []; // an array of results.
for($qr = 20; $qr <= 40; $qr++) {
for($ps = 20; $ps <= 40; $ps++) {
for($xy = 100; $x <= 200; $xy++) {
$den = ($qr + 1000) - $ps;
$num = $xy;
$value = $num/$den; // decimalised
$c_diff[] = float2frac($num, $den);
/***
What is your criteria for success?
***/
if(success){
$qr_text = "Q,R<BR>";
$qr_text .= find_equivs($qr,10,20);
$sp_text = "S,P<BR>";
$sp_text .= find_equivs($sp,10,20);
$xy_text = "X,Y<BR>";
$xy_text .= find_equivs($sp,50,100);
}
}
}
}
This should do only a small percentage of the original looping.
I guess this isn't the entire block of code you are using.
This loop creates 50*50*10*10*10*10 = 25.000.000 Fraction objects. Consider using PHP's unset() to clean up memory, since you are allocating memory to create objects, but you never free it up.
editing for clarification
When you create anything in PHP, be it variable, array, object, etc. PHP allocates memory to store it and usually, the allocated memory is freed when script execution ends.
unset() is the way to tell PHP, "hey, I don't need this anymore. Can you, pretty please, free up the memory it takes?". PHP takes this into consideration and frees up the memory, when its garbage collector runs.
It is better to prevent memory exhaustion rather than feeding your script with more memory.
Allowed memory size of 134217728 bytes exhausted
134217728 bytes = 134.218 megabytes
Can you try this?
ini_set('memory_limit', '140M')
/* loop code below */

Round # With 1% Accuracy and Convert to Scientific Notation

I have millions of long numbers (5216672577) in CSV data files and I'd like to reduce the file size. I plan to do this by rounding as many of the trailing digits as possible to 0, while staying within X% accuracy to the original number. Then I will convert the numbers to scientific notation. I prefer the format 103e7 to 1.03E+7. The period and plus symbols add unnecessary bytes. I'm working with integers.
Update - I got it to work:
for($i = 9000; $i < 11000; $i++) {
echo notation($i), "\r\n<br>";
}
// Round and abbreviate an integer
function notation($int, $precision=0.01) {
// We cannot shorten small numbers
if(is_int($int) && $int >= 1000) {
$best = $int;
// For each decimal place
$l = strlen($int);
for($i = 3; $i <= $l - 1; $i++) {
// Round to the deciaml place
$newInt = round($int, -$i);
// Check precision
$ratio = $int / $newInt;
if($ratio < (1 - $precision) || $ratio > (1 + $precision)) {
break;
}
// Save the best option
$best = $newInt;
}
// Count and remove trailing zeros
$l = strlen($best);
$best = rtrim($best, '0');
$i = $l - strlen($best);
// Check that we can actually shorten the int
if ($i >= 3) {
// Add scientific Notation
return $best . 'e' . $i;
}
}
return $int;
}

How do I roll over when doing math on an array of integers?

So I am trying to do math on an array of integers while enforcing a maximum integer in each piece of the array. Similar to this:
function add($amount) {
$result = array_reverse([0, 0, 0, 100, 0]);
$max = 100;
for ($i = 0; $i < count($result); ++$i) {
$int = $result[$i];
$new = $int + $amount;
$amount = 0;
while ($new > $max) {
$new = $new - $max;
++$amount;
}
$result[$i] = $new;
}
return array_reverse($result);
}
add(1); // [0, 0, 0, 100, 1]
add(100); // [0, 0, 0, 100, 100]
add(101); // [0, 0, 1, 0, 100]
So what I have above works but it is slow when adding larger integers. I've tried to do this with bitwise shifts and gotten close but I just can't get it to work for some reason. I think I need a third-party perspective. Does anyone have some tips?
The part that is taking up the majority of the time is the while loop. You are reducing the value down repeatedly until you have a sub-100 value. However, using PHP to loop down like that takes an incredible amount of time (a 12-digit integer clocked in at over 20 seconds on my local machine). Instead, use multiplication and division (along with an if). It is magnitudes faster. The same 12-digit integer took less than a second to complete with this code:
function add($amount) {
$result = array_reverse([0, 0, 0, 100, 0]);
$max = 100;
for ($i = 0, $size = count($result); $i < $size; ++$i) {
$int = $result[$i];
$new = $int + $amount;
$amount = 0;
if( $new > $max ) {
$remainder = $new % $max;
// Amount is new divided by max (subtract 1 if remainder is 0 [see next if])
$amount = ((int) ($new / $max));
// If remainder exists, new is the the number of times max goes into new
// minus the value of max. Otherwise it is the remainder
if( $remainder == 0 ) {
$amount -= 1;
$new = $new - ((($new / $max) * $max) - $max);
} else {
$new = $remainder;
}
}
$result[$i] = $new;
}
return array_reverse($result);
}
Also note that I moved your count($result) call into the variable initialization section of the for loop. When it is inside the expression section it gets executed each time the for loop repeats which can also add to the overall time of executing the function.
Also note that with a large math change like this you may want to assert a range of values you expect to calculate to ensure there are no outliers. I did a small range and they all came out the same but I encourage you to run your own.
Use min($max, $number) to get $number limited to $max.
for ($i = 0; $i < count($result); ++$i) {
$result[$i] = min($max, $result[$i] + $amount);
}

PHP Random String Generator... not so random

I always thought my code generated pretty random strings but I've been pressing F5 for about 10 minutes and I display 10 strings at once and I have had THREE DUPLICATES, UNIBON, ZANOPE and ZOTAXS.
Can anyone explain why this is when I though there code be 26^6 possibilities?
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$pass = '';
for ($i = 0; $i < $len; $i++){
$pass .= $chars[(rand() % strlen($chars))];
}
return $pass;
Any advice would be much appreciated.
Thanks
Using mt_rand the first duplicate takes on average between 10 and 60 seconds, that seems okay doesn't it?
echo 'start: '.date('H:i:s');
for ($i = 1; ; $i++) {
$testarr[] = passGen(6);
$new = passGen(6);
if (in_array($new,$testarr)){
echo '<br>end: '.date('H:i:s');
echo '<br>string: '.$new;
echo '<br>count: '.count($testarr);
break;
}
}
Why don't you hash a random number then take a random substring from the hash?
You should try this :
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$pass = '';
$length = strlen($chars)-1;
for ($i = 0; $i < 10; $i++){
$randomNumber = rand(0,$length);
$pass .= substr($chars,$randomNumber,1);
}
return $pass;
You should use mt_rand.
mt_rand is way better than rand. From the PHP manual
Many random number generators of older libcs have dubious or unknown characteristics and are slow. By default, PHP uses the libc random number generator with the rand() function. The mt_rand() function is a drop-in replacement for this. It uses a random number generator with known characteristics using the » Mersenne Twister, which will produce random numbers four times faster than what the average libc rand() provides.
That aside you can use this function instead to generate random strings with the length you wish ;)
function random($length = 10)
{
$chars = 'BCDFGHJKLMNPQRSTVWXYZAEIUO';
for ($i = 0; $i < $length; $i++)
{
$pass .= ($i%2) ? $chars[mt_rand(19, 25)] : $chars[mt_rand(0, 18)];
}
return $pass;
}
This function can be used easily to generate CAPTCHAs too ;)
I think that's just the nature of the beast. Increase the length to 7 and add some more characters and the probability of duplicates goes down. This is what I used to test:
<?php
$len = 6;
# Remove characters that can be mistaken for others, I,0,L,1 and 0
$chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
for ($j=0;$j<100000;$j++) {
$pass = '';
for ($i = 0; $i < $len; $i++){
$pass .= $chars[(rand() % strlen($chars))];
}
if(isset($saved[$pass])) {
echo "Password \"$pass\" on iteration $j has duplicate(s) from iteration(s) " . implode(',',$saved[$pass]) . "\n";
}
$saved[$pass][] = $j;
}
?>
Using mt_rand() vs rand() didn't change the output much

How do I create a permutation of a string (10 characters long or more)?

I got a task that makes me a little crazy, there is the section dealing with word permutations, after I browsing the internet I found a function to do permutations, as shown below:
function permute($str) {
if (strlen($str) < 2) {
return array($str);
}
$permutations = array();
$tail = substr($str, 1);
foreach (permute($tail) as $permutation) {
$length = strlen($permutation);
for ($i = 0; $i <= $length; $i++) {
$permutations[] = substr($permutation, 0, $i) . $str[0] . substr($permutation, $i);
}
}
return $permutations;
}
this to show the result:
print_r(array_unique(permute("abcdefghi"))); // found 362880
print_r(array_unique(permute("abcdefghij"))); // error
The problem is, this function is only able to perform all the permutations of 9 characters (approximately 362880 combinations, with a long time and make the browser not responding for tinytime). When trying to perform a permutation of up to 10 characters, an error message will appear:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 35 bytes)
Do you have a solution or another way to do permutations with a length of 10 characters or more?
The number of permutations of a string of length N is N!
So if you're just looking for the number of permutations, this will do:
function factorial($n) {
if( $n == 0) return 1;
if( $n < 3) return $n;
return $n*factorial($n-1);
}
function permute($str) {
return factorial(strlen($str));
}
If, however, you are trying to get a random one of those permutations, try this:
function permute($str) {
$l = strlen($str);
$a = str_split($str);
$ret = "";
while($l > 0) {
$i = rand(0,$l-1);
$ret .= $a[$i];
array_splice($a,$i,1);
$l--;
}
return $ret;
}
If you are trying to brute-force all N! permutations, try:
ini_set("memory_limit",-1);
set_time_limit(0);
// your brute-force code here
If none of these answer your question, please clarify ;)

Categories