Apologies - I'm not even sure I'm using the right terminology here.
I have a series of confidential documents and I'm creating a bitmask (?) to represent which of those documents a given user can view. (1 represents Doc1, 2 represents Doc2, 4 represents Doc3, 8 represents Doc4, 16 represents Doc5, etc.)
So, if a user can view docs 1, 2, and 5, the bitmask will be 19.
Where I'm really stumped, though, is how to reverse calculate the individual values "stored" in the bitmask. Currently, I'm using
if($docs2view==1) {
$nextdoc = 1;
}
if($docs2view==2) {
$nextdoc = 2;
}
. . .
if($docs2view==127) {
$nextdoc = 1;
}
which is REALLY tedious and obviously very inefficient. Can someone point me toward the right way to do this?
You need a bitwise-and:
if( $docs2view & 1 ) {...}
if( $docs2view & 2 ) {...}
if( $docs2view & 4 ) {...}
if( $docs2view & 8 ) {...}
if( $docs2view & 16 ) {...}
Here, I'm testing individual bits. If the bit is set, the condition will be non-zero (hence will evaluate to 'true').
You could possibly avoid a lot of code repetition by putting this in a loop and using the bit-shift operators (>> and <<).
You said:
Thank you, paddy! But I only need the lowest value that evaluates
true. How do I tell the loop to stop as soon as it finds that?
You can either convert those statements to elseif so that only the first becomes true, or you can do this (assuming you only check the first 8 bits):
$nextdoc = 0;
for( $i = 0; $i < 8; $i++ ) {
if( $docs2view & (1<<$i) ) {
$nextdoc = $i + 1;
break;
}
}
Related
Example, I have 4 ranges:
0 - 1.25
1.26 - 2.45
2.46 - 5
5.01 - infinity
And I have a floating point number to compare with: 1.2549999999.
I need to check to what range this number belongs.
I have the following code, but I'm not convinced it's efficient enough
$comparedNumber = 1.2549999999;
if (0 < $comparedNumber && round($comparedNumber, 2) <= round(1.25,2)) {
$selectedRange = 'Range 1';
} elseif ( round(1.26,2) <= round($comparedNumber, 2) && round($comparedNumber, 2) <= round(2.45,2)) {
$selectedRange = 'Range 2';
} elseif ( round(2.46,2) <= round($comparedNumber, 2) && round($comparedNumber, 2) <= round(5,2)) {
$selectedRange = 'Range 3';
} elseif ( round(5.01,2) <= round($comparedNumber, 2) ) {
$selectedRange = 'Range 4';
} else {
$selectedRange = 'Range not exist';
}
print_r($selectedRange);
Sample here
Your problem is poorly thought out boundaries, and trying to use equality to compare floating points. Rounding is not a solution: the return value of round() is still a floating point number.
For your "ranges" you have actually three boundaries: 1.26, 2.46, and 5.01.
A generalized solution would be:
<?php
$numbers = [1.2549999999, 1.28012, 2.01212, 4.012, 5.0000012, 5.012121001, -0.12];
$boundaries = [1.26, 2.46, 5.01];
function checkRange(float $number, array $boundaries): int {
if ($number < 0) {
return -1;
}
foreach ($boundaries as $i => $boundary) {
if ($number < $boundary) {
return $i + 1;
break;
}
}
return 4;
}
foreach ($numbers as $number) {
echo "$number at Range ", checkRange($number, $boundaries), "\n";
}
/*
Output:
1.2549999999 at Range 1
1.28012 at Range 2
2.01212 at Range 2
4.012 at Range 3
5.0000012 at Range 3
5.012121001 at Range 4
-0.12 at Range -1
*/
As seen working here.
Note that the solution in the other answer fails to account for numbers in the range 4.
For this exercise, I treat numbers below 0 as "out of range", and put them in "range -1". What to do exactly with those is up to you.
This works for any given set of boundaries (as long as they are ordered), and does not need rounding at any point, since it's moot for the comparison. A number is less than the boundary, or it's not.
Your current ranges could potentially have gaps: 1.250001 would not be <= 1.25, but would not be >= 1.26 either. You've tried to handle that with round(), but the result of that is still a floating point number, and binary floating point does not accurately represent decimals. This isn't unique to PHP, it's something you'll encounter in basically every programming language (a very few have a separate type for fixed decimal numbers, trading performance for accuracy).
In particular, writing round(1.25, 2) is never going to result in a different value from writing 1.25, because the compiler will already have picked the closest floating point value to 1.25 possible.
The simple fix is to use the same boundary each time, but exclude equal values on second mention: rather than >= 1.26 in the second range, use > 1.25. However, that makes it obvious that you have redundant tests anyway, because if something doesn't fall into the <= 1.25 bucket, you already know that it is > 1.25, so don't need to test that again.
For readability (and an immeasurably tiny amount of performance), I would assign a local variable to round($comparedNumber, 2) rather than pasting it into each check. You may also decide you don't want that rounding - its effect will be to put 1.251 into the ">0, <=1.25" bucket rather than the ">1.25, <=2.45" bucket.
So it simplifies down to this:
$comparedNumber = 1.2549999999;
$roundedNumber = round($comparedNumber, 2);
if ($roundedNumber <= 0) {
$selectedRange = 'Range not exist';
} elseif ($roundedNumber <= 1.25) {
$selectedRange = 'Range 1';
} elseif ($roundedNumber <= 2.45) {
$selectedRange = 'Range 2';
} elseif ($roundedNumber <= 5) {
$selectedRange = 'Range 3';
} else {
$selectedRange = 'Range 4';
}
Since you now only need one number to define each range, making this into a loop is simple:
foreach ( $ranges as $rangeName => $rangeBoundary ) {
if ( $roundedNumber <= $rangeBoundary ) {
$selectedRange = $rangeName;
break; // stops the loop carrying on with the next test
}
}
In addition to other good answers discussing the weak idea of rounding:
Performance question too, maybe there are built in function or some trick
If the number of ranges was large, like 10+, code could efficiently determine the range via a binary search on the list of limits. With 10 limits, this would take at most 4 iterations O(log n),rather than 10 (O(n)). With 100 limits, takes at most 7.
If the ranges were approximately linearly distributed, an average range look would be O(1).
With real life distributions, a combination of the above 2 strategies is best.
With a fixed group of 4, simply test against the middle one, and then the remaining quarter.
I am learning PHP. I decided to adapt a solution to the famous FizzBuzz problem from Javascript to PHP, just to see how JS and PHP compare.
For those who forgot what the FizzBuzz problem is :
Write a short program that prints each number from 1 to 100 on a new
line. For each multiple of 3, print "Fizz" instead of the number.
For each multiple of 5, print "Buzz" instead of the number. For
numbers which are multiples of both 3 and 5, print "FizzBuzz" instead
of the number.
I am using a lot of short-circuit evaluations in the following examples.
Here's the clever solution (not written by me) that I adapted to PHP:
Works great!
Then, for the sake of challenge, I decided to try and rewrite it in one single line.
Here's how I started:
Seems like a good start. My condition in the while loop: if $i is not set set, then I set it to zero, but if it is already set, I skip the first part and check if it's inferior to 100.
As you can see on this picture, my loop works.
Since my goal is to write it in one line, I need to increment $i inside my conditional statement, just as with my previous multi-line solution. But when I write $i++ < 100 as before, something weird happens. My loop only runs once and stops.
Very weird indeed.
Even weirder, if I use both increments (one in the condition and one in the loop), the loop then works fine, and applies both increments.
I'm puzzled. Is it because there is a safeguard for infinite loops somewhere? With all those short-circuit evaluations, even my IDE PHP Storm says 'variable $i is probably undefined'. But it gets defined, and my loop works fine under certain conditions. What did I miss ?
EDIT:
Here's the code:
Multi-line working FizzBuzz:
$i = 0;
while ($i++ < 100) {
$msg = '';
($i % 3) || ($msg = $msg . 'Fizz');
($i % 5) || ($msg = $msg . 'Buzz');
echo $msg . "\n";
}
Weird loop iteration (you can delete the increment either in the loop or in the condition, or leave both to see the different effects):
while ( (!isset($i) && ($i=0 || true) ) || ($i++ < 100) ) {
echo $i . "\n";
$i = $i +1;
}
if $i is not set set, then I set it to zero
This is not quite right. Here's what's going on.
Your statement ($i=0 || true) sets $i to TRUE.
PHP's type juggling with print "1" for "$i". Consider $v = TRUE; echo "$v"; to see this in effect.
On the next iteration, your second condition is evaluated as TRUE < 100 which evaluates to FALSE, thereby exiting the loop.
So, in order to fix your problem, simply drop the || true and be on your merry way.
$i=0 || true results in $i being true.
true++ doesn’t actually do anything.
true < 100 is false.
echo true outputs 1.
An explicit true + 1 creates 2.
OMG, Yes! I thought I needed the || true part because ( !isset($i) && ( $i = 0 ) ) will either do both sides of the &&, or neither. I never thought that ( $i = 0 ) would evaluate to "true". But it looks like it does :)
OBSOLETE COMMENT: I found the origin of the quirk. Not sure why it happens though.
If I rewrite $i++ as ($i = $i + 1), it works fine.
while ( (!isset($i) && ($i=0 || true) ) || (($i = $i + 1) < 100) ) {
echo $i . "\n";
}
I'm reading "PHP 7 Data Structures and Algorithms" chapter "Shortest path using the Floyd-Warshall algorithm"
the author is generating a graph with this code:
$totalVertices = 5;
$graph = [];
for ($i = 0; $i < $totalVertices; $i++) {
for ($j = 0; $j < $totalVertices; $j++) {
$graph[$i][$j] = $i == $j ? 0 : PHP_INT_MAX;
}
}
i don't understand this line :
$graph[$i][$j] = $i == $j ? 0 : PHP_INT_MAX;
looks like a one line if statement
is it the same as ?
if ($i == $j) {
$graph[$i][$j] = 0;
} else {
$graph[$i][$j] = PHP_INT_MAX;
}
what is the point of using PHP_INT_MAX ?
at the end what does the graph look like ?
You've correctly understood the ternary (? :) operator
To answer the other part of your question, have a look if the following makes sense to you.
First:
The author initializes the $graph array using the following code:
<?php
$totalVertices = 5; // total nodes (use 0, 1, 2, 3, and 4 instead of A, B, C, D, and E, respectively)
$graph = [];
for ($i = 0; $i < $totalVertices; $i++) {
for ($j = 0; $j < $totalVertices; $j++) {
$graph[$i][$j] = $i == $j ? 0 : PHP_INT_MAX;
}
}
which results in the following matrix
All the nodes(vertices) on the main diagonal(grey) are set to 0 as a node's distance to itself equals 0.
All the remaining nodes in the 'matrix' are set to PHP_INT_MAX (the largest integer supported) - we'll see why this is in a minute.
Second:
The author then sets the distances between the nodes that have a direct connection(edges), writing them manually to the $graph array, as follows:
$graph[0][1] = $graph[1][0] = 10;
$graph[2][1] = $graph[1][2] = 5;
$graph[0][3] = $graph[3][0] = 5;
$graph[3][1] = $graph[1][3] = 5;
$graph[4][1] = $graph[1][4] = 10;
$graph[3][4] = $graph[4][3] = 20;
This results in the following 'matrix' stored in array $graph (green: edge distances):
So why does the author use PHP_INT_MAX for the nodes that are not directly connected(the non-edges)?
The reason is, because it allows for the algorithm to work with
node-connection(edge) distances up to and including PHP_INT_MAX.
In this particular example, any number smaller than 20 in stead of PHP_INT_MAX in the ternary would warp the outcomes of the algorithm - it would spit out wrong results.
Or another way to look at this, in this particular example the author could have just used any number bigger than 20 in stead of PHP_INT_MAX to get satisfactory results from the algorithm,
because the biggest distance between two directly connected nodes in this case equals 20. Use any number smaller than 20 and the results will come out wrong.
You can give it a try, and test:
$graph[$i][$j] = $i == $j ? 0 : 19;
the algorithm will now tell us that the shortest distance between A to E - i.e. $graph[0][4] equals 19... WRONG
So using PHP_INT_MAX here gives 'leeway', it allows for the algorithm to work successfully with edge distances smaller than or equal to 9223372036854775807 (the largest int that can be stored on a 64 bit system),
or 2147483647 (on a 32 bit system).
You have two questions here.
The first is regarding the syntax condition ? val_if_true : val_if_false. This is called the "ternary operator". Your assessment regarding the behavior is correct.
The second is regarding the use of PHP_INT_MAX. All distances between two nodes are being initialized to one of two values: 0 if nodes i and j are the same node (i.e. a vertex), and PHP_INT_MAX if the nodes are not the same (i.e. an edge). That is, a node's distance to itself is 0 and a node's distance to any other node is the largest integer value PHP recognizes. The reason for this is that the Floyd-Warshall algorithm utilizes the concept of "infinity" to represent minimum distances that have not yet been calculated, but as there is no concept of "infinity" in PHP, the value PHP_INT_MAX is being used as a stand-in for it.
I have an array of values that represent points on a line chart:
$temperatures = [23, 24, null, '', 25, '', '', null];
I'm using PHP4, but I think it can be answered in any language.
Array contains only numbers, nulls and empty strings.
Numbers represent temperatures, nulls mean that the instruments weren't working and empty strings represent neither (instruments are working, just not measuring anything).
Points must (in most cases) be connected, since it's a line chart.
I have a variable $gap that corresponds to each point and tells whether this point is connected to the next point. If it is set to true, than the points are not connected (false otherwise). For example, $gap for temperatures[0] must be set to false, since the line is drawn between temperatures[0] and temperatures[1](they are both valid temperatures). $gap fortemperatures[1]andtemperatures[2]` must be true, since there is null following. And so on.
When there is null the $gap is absolutely true. For numbers and empty strings, it depends on: if a null follows, gap is true; if a number follows, gap is false. If empty string follows, we must check if afterwards comes null or number and apply the previous sentence accordingly. If there are just empty strings following, gap is true. Here is my code that is working too slow, but produce correct results:
$limit = count($temperatures);
for ($i = 0; $i <= limit; $i++) {
$next_is_number = false;
if (is_null($temperatures[i]) {
$gap = true;
} else {
for ($y = $i + 1; $i <= limit; $i++) {
if (is_null($temperatures[$y]) {
break;
} elsif (is_numeric($temperatures[$y]) {
$next_is_number = true;
break;
}
}
if ($next_is_number) {
$gap = false;
} else {
$gap = true;
}
}
}
How can I speed it up?
Your code checks whether there is a a gap somewhere in your line chart or not.
So once a gap is found, there no reason to continue in the outer for-loop. Think of a chart of 1000 values, if there is a gap between the first two values it makes no sense to continue checking the other 998 values.
Thus, the first thing I would recommend is to set $gap = false at the beginning and to leave the loop once $gap is true. You could do that either with
1.) break (not so elegant),
2.) extract your code to a method and add a return-statement or
3.) adding a condition in the for-loop. I am not familiar with php but in most languages it is possible to do it like this:
$gap = false;
$limit = count($temperatures);
for ($i = 0; $i <= limit && !$gap; $i++) {
[...]
So once $gap is true, the outer for-loop is left.
Iterate through backwards, remembering the last valid value and putting that in when you see an empty string. Then it's O(n) worst case, not O(n^2).
Alternatively, you can work from $y - 1 to $x (or vice versa) after the inner loop, setting the values of your gaps array / outputting values, then skip past all the ones you've just done ($x = $y). This is also O(n).
Then, once you've got the algorithm as fast as you can, you can ditch PHP and write it in something like Rust or C. (I don't recall any true arrays in the language, so they're always going to be slow.)
I'm attempting to solve Project Euler in PHP and running into a problem with my for loop conditions inside the while loop. Could someone point me towards the right direction? Am I on the right track here?
The problem, btw, is to find the sums of all prime numbers below 2,000,000
Other note: The problem I'm encountering is that it seems to be a memory hog and besides implementing the sieve, I'm not sure how else to approach this. So, I'm wondering if I did something wrong in the implementation.
<?php
// The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
// Additional information:
// Sum below 100: 1060
// 1000: 76127
// (for testing)
// Find the sum of all the primes below 2,000,000.
// First, let's set n = 2 mill or the number we wish to find
// the primes under.
$n = 2000000;
// Then, let's set p = 2, the first prime number.
$p = 2;
// Now, let's create a list of all numbers from p to n.
$list = range($p, $n);
// Now the loop for Sieve of Eratosthenes.
// Also, let $i = 0 for a counter.
$i = 0;
while($p*$p < $n)
{
// Strike off all multiples of p less than or equal to n
for($k=0; $k < $n; $k++)
{
if($list[$k] % $p == 0)
{
unset($list[$k]);
}
}
// Re-initialize array
sort ($list);
// Find first number on list after p. Let that equal p.
$i = $i + 1;
$p = $list[$i];
}
echo array_sum($list);
?>
You can make a major optimization to your middle loop.
for($k=0; $k < $n; $k++)
{
if($list[$k] % $p == 0)
{
unset($list[$k]);
}
}
By beginning with 2*p and incrementing by $p instead of by 1. This eliminates the need for divisibility check as well as reducing the total iterations.
for($k=2*$p; $k < $n; $k += $p)
{
if (isset($list[k])) unset($list[$k]); //thanks matchu!
}
The suggestion above to check only odds to begin with (other than 2) is a good idea as well, although since the inner loop never gets off the ground for those cases I don't think its that critical. I also can't help but thinking the unsets are inefficient, tho I'm not 100% sure about that.
Here's my solution, using a 'boolean' array for the primes rather than actually removing the elements. I like using map,filters,reduce and stuff, but i figured id stick close to what you've done and this might be more efficient (although longer) anyway.
$top = 20000000;
$plist = array_fill(2,$top,1);
for ($a = 2 ; $a <= sqrt($top)+1; $a++)
{
if ($plist[$a] == 1)
for ($b = ($a+$a) ; $b <= $top; $b+=$a)
{
$plist[$b] = 0;
}
}
$sum = 0;
foreach ($plist as $k=>$v)
{
$sum += $k*$v;
}
echo $sum;
When I did this for project euler i used python, as I did for most. but someone who used PHP along the same lines as the one I did claimed it ran it 7 seconds (page 2's SekaiAi, for those who can look). I don't really care for his form (putting the body of a for loop into its increment clause!), or the use of globals and the function he has, but the main points are all there. My convenient means of testing PHP runs thru a server on a VMWareFusion local machine so its well slower, can't really comment from experience.
I've got the code to the point where it runs, and passes on small examples (17, for instance). However, it's been 8 or so minutes, and it's still running on my machine. I suspect that this algorithm, though simple, may not be the most effective, since it has to run through a lot of numbers a lot of times. (2 million tests on your first run, 1 million on your next, and they start removing less and less at a time as you go.) It also uses a lot of memory since you're, ya know, storing a list of millions of integers.
Regardless, here's my final copy of your code, with a list of the changes I made and why. I'm not sure that it works for 2,000,000 yet, but we'll see.
EDIT: It hit the right answer! Yay!
Set memory_limit to -1 to allow PHP to take as much memory as it wants for this very special case (very, very bad idea in production scripts!)
In PHP, use % instead of mod
The inner and outer loops can't use the same variable; PHP considers them to have the same scope. Use, maybe, $j for the inner loop.
To avoid having the prime strike itself off in the inner loop, start $j at $i + 1
On the unset, you used $arr instead of $list ;)
You missed a $ on the unset, so PHP interprets $list[j] as $list['j']. Just a typo.
I think that's all I did. I ran it with some progress output, and the highest prime it's reached by now is 599, so I'll let you know how it goes :)
My strategy in Ruby on this problem was just to check if every number under n was prime, looping through 2 and floor(sqrt(n)). It's also probably not an optimal solution, and takes a while to execute, but only about a minute or two. That could be the algorithm, or that could just be Ruby being better at this sort of job than PHP :/
Final code:
<?php
ini_set('memory_limit', -1);
// The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
// Additional information:
// Sum below 100: 1060
// 1000: 76127
// (for testing)
// Find the sum of all the primes below 2,000,000.
// First, let's set n = 2 mill or the number we wish to find
// the primes under.
$n = 2000000;
// Then, let's set p = 2, the first prime number.
$p = 2;
// Now, let's create a list of all numbers from p to n.
$list = range($p, $n);
// Now the loop for Sieve of Eratosthenes.
// Also, let $i = 0 for a counter.
$i = 0;
while($p*$p < $n)
{
// Strike off all multiples of p less than or equal to n
for($j=$i+1; $j < $n; $j++)
{
if($list[$j] % $p == 0)
{
unset($list[$j]);
}
}
// Re-initialize array
sort ($list);
// Find first number on list after p. Let that equal p.
$i = $i + 1;
$p = $list[$i];
echo "$i: $p\n";
}
echo array_sum($list);
?>