This question is born out of pure laziness and the desire to do more with less code.
Say I have a variable $x which needs to be greater than 0 and less than 12, what is the fastest (least amount of code written) way to check. Is there a faster way than this.
if($x < 0 || $x > 12) {
die("invalid x value");
}
It would be nice (and I think some languages have it) to do this:
if(0 > $x > 12) {
die("invalid x value");
}
Very curious to see if there is some PHP magic I am missing out on.
You can use filter_var as a native PHP function : http://de2.php.net/manual/en/function.filter-var.php But I don't find it any better, as you will need to pass an array with min and max range, which is not fast, nor short.
Maybe a user-defined function for this will fit? Yes, you will need to write the code once, but only once.
function between($value, $from, $to) {
if ($value < $from || $value > $to) {
return false;
}
return true;
}
The function return false, if the value is less than the min bound, or greater than the max bound. Otherwise it returns true. So if you need to stop you script, if the value is NOT between, you would need to ask for the false response if(!between...
So you only call it this way:
$x = 14;
if(!between($x, 0, 12)) {
die("invalid x value");
}
Output:
invalid x value
if, for example your $x is 5 and you want to check if it's between, and if it is - to continue the script, you ask for the true response.
if(between($x, 0, 12)
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 currently teaching myself web development/ programming and to learn php i have built a simple program. The program takes user input and based on a series of math algorithms and calculates 7 random lottery numbers. The code is working fine but i want to improve it. The code is very repetitive and i want to simplify it by creating my own functions. I have created the first function that takes the users input, simply does some maths and then returns some values.
For Example...
<?php
function some_maths($int1 $int2 $int3){
$x = $int1 + $int2;
$y = $int2 * $int3;
$z = $y * $x;
return $x
....}
So this is pretty straight forward, but what i want to do now is take the values of X, Y, Z and create a function that checks to make sure they're not matching, or that they're not less than 1 or greater than 59. I used a while loop in my original code that goes like this:
while($x == $y || $x == $z || $x <1 || $x >59){
if( x> 59 || x < 1){
if (x<1){
do{ $x+=$int}while($x <1);
}elseif ($x > 59){
do{ $x-=$int}while($x >59);
}else $x++;
}
This seems to work fine but i don't want to have to repeat the same code over and over. I am sure there has to be a better way? Could i put the values into an array and maybe do it that way? What would be the best solution for this?
Your question is kind of vague but if I had to write a function to check if three numbers weren't equal and were < 59 and >1 this is how I would do it
function validateNumbers($x , $y , $z)
{
if(equal($x,$y)) return false;
if(equal($x,$z)) return false;
if(equal($y,$z)) return false;
if($x>59||$x<1) return false;
if($y>59||$y<1) return false;
if($z>59||$z<1) return false;
return true;
}
function equal($x , $y)
{
if($x == $y)return true;
else return fasle;
}
So far I only see two (pretty straightforward) things:
Your function prototype in the first example is missing commas between the parameters. Instead of function some_maths($int1 $int2 $int3) it should read function some_maths($int1, $int2, $int3).
In your second example a closing } is missing. But if I am interpreting your stuff correctly, the outer if-clause is redundant. Thus, the snippet can be simplified to:
Second example:
while($x == $y || $x == $z || $x <1 || $x >59){
if (x<1){
do{ $x+=$int}while($x <1);
}
elseif ($x > 59){
do{ $x-=$int}while($x >59);
}
else $x++;
}
There may be more room for improvement (e.g. slim down the condition of the outer while loop) - but for that we would need more context (what happens before your loop, what is $int, ...).
I need to find the value of x where the variance of two results (which take x into account) is the closest to 0. The problem is, the only way to do this is to cycle through all possible values of x. The equation uses currency, so I have to check in increments of 1 cent.
This might make it easier:
$previous_var = null;
$high_amount = 50;
for ($i = 0.01; $i <= $high_amount; $i += 0.01) {
$val1 = find_out_1($i);
$val2 = find_out_2();
$var = variance($val1, $val2);
if ($previous_var == null) {
$previous_var = $var;
}
// If this variance is larger, it means the previous one was the closest to
// 0 as the variance has now started increasing
if ($var > $previous_var) {
$l_s -= 0.01;
break;
}
}
$optimal_monetary_value = $i;
I feel like there is a mathematical formula that would make the "cycling through every cent" more optimal? It works fine for small values, but if you start using 1000's as the $high_amount it takes quite a few seconds to calculate.
Based on the comment in your code, it sounds like you want something similar to bisection search, but a little bit different:
function calculate_variance($i) {
$val1 = find_out_1($i);
$val2 = find_out_2();
return variance($val1, $val2);
}
function search($lo, $loVar, $hi, $hiVar) {
// find the midpoint between the hi and lo values
$mid = round($lo + ($hi - $lo) / 2, 2);
if ($mid == $hi || $mid == $lo) {
// we have converged, so pick the better value and be done
return ($hiVar > $loVar) ? $lo : $hi;
}
$midVar = calculate_variance($mid);
if ($midVar >= $loVar) {
// the optimal point must be in the lower interval
return search($lo, $loVar, $mid, $midVar);
} elseif ($midVar >= $hiVar) {
// the optimal point must be in the higher interval
return search($mid, $midVar, $hi, $hiVar);
} else {
// we don't know where the optimal point is for sure, so check
// the lower interval first
$loBest = search($lo, $loVar, $mid, $midVar);
if ($loBest == $mid) {
// we can't be sure this is the best answer, so check the hi
// interval to be sure
return search($mid, $midVar, $hi, $hiVar);
} else {
// we know this is the best answer
return $loBest;
}
}
}
$optimal_monetary_value = search(0.01, calculate_variance(0.01), 50.0, calculate_variance(50.0));
This assumes that the variance is monotonically increasing when moving away from the optimal point. In other words, if the optimal value is O, then for all X < Y < O, calculate_variance(X) >= calculate_variance(Y) >= calculate_variance(O) (and the same with all > and < flipped). The comment in your code and the way have you have it written make it seem like this is true. If this isn't true, then you can't really do much better than what you have.
Be aware that this is not as good as bisection search. There are some pathological inputs that will make it take linear time instead of logarithmic time (e.g., if the variance is the same for all values). If you can improve the requirement that calculate_variance(X) >= calculate_variance(Y) >= calculate_variance(O) to be calculate_variance(X) > calculate_variance(Y) > calculate_variance(O), you can improve this to be logarithmic in all cases by checking to see how the variance for $mid compares the the variance for $mid + 0.01 and using that to decide which interval to check.
Also, you may want to be careful about doing math with currency. You probably either want to use integers (i.e., do all math in cents instead of dollars) or use exact precision numbers.
If you known nothing at all about the behavior of the objective function, there is no other way than trying all possible values.
On the opposite if you have a guarantee that the minimum is unique, the Golden section method will converge very quickly. This is a variant of the Fibonacci search, which is known to be optimal (require the minimum number of function evaluations).
Your function may have different properties which call for other algorithms.
Why not implementing binary search ?
<?php
$high_amount = 50;
// computed val2 is placed outside the loop
// no need te recalculate it each time
$val2 = find_out_2();
$previous_var = variance(find_out_1(0.01), $val2);
$start = 0;
$end = $high_amount * 100;
$closest_variance = NULL;
while ($start <= $end) {
$section = intval(($start + $end)/2);
$cursor = $section / 100;
$val1 = find_out_1($cursor);
$variance = variance($val1, $val2);
if ($variance <= $previous_var) {
$start = $section;
}
else {
$closest_variance = $cursor;
$end = $section;
}
}
if (!is_null($closest_variance)) {
$closest_variance -= 0.01;
}
I have the following code in production that appears to be causing an infinite loop.
$z=1;
while (!$apns = $this->getApns($streamContext) && $z < 11)
{
myerror_log("unable to conncect to apple. sleep for 2 seconds and try again");
$z++;
sleep(2);
}
How are the precedence rules getting applied that cause this behavior?
http://php.net/manual/en/language.operators.precedence.php
I see this note in the docs:
Although = has a lower precedence than most other operators, PHP will
still allow expressions similar to the following: if (!$a = foo()), in
which case the return value of foo() is put into $a.
Which makes me think the the = should be evaluated first. then the ! then the &&, which would not cause an infinite loop.
Your code is evaluating like this:
while (!($apns = ($this->getApns($streamContext) && ($z < 11))))
which is why you see the infinite loop (as soon as $z >= 11, $apns is false, so the condition is always true). The reason for this precedence is that the special rules only apply to ! on the left of the assignment being valid (having lower precedence than =). It has no effect on the boolean operator on the right, which behaves as it would in any sane language.
Your style is bad. Try this, which is much more readable and only differs in the final value of $z (and if that's important you can tweak the break statement.
for( $z = 1; $z < 11; ++ $z ) {
// note extra brackets to make it clear that we intend to do assignment not comparison
if( ($apns = $this->getApns($streamContext)) ) {
break;
}
myerror_log("unable to conncect to apple. sleep for 2 seconds and try again");
sleep(2);
}
Your code is clear example of why it's good habit to always put all the conditions in brackets (and the same applies to code block. Even oneliners should be surrounded by { and }). So instead of error-prone:
while (!$apns = $this->getApns($streamContext) && $z < 11)
do
while (!($apns = $this->getApns($streamContext)) && ($z < 11))
and you will be safe.
I can check if some integer value (which comes from user input, and thus should be filtered) is in specific range like this:
<?php
$size=50;
var_dump(in_array($size,range(1,100)));
?>
which will echo true if the size in range 1 to 100. Of course the other method is using filters:
<?php
$size=50;
$int_options = array("options"=>
array("min_range"=>0, "max_range"=>256));
var_dump(filter_var($size, FILTER_VALIDATE_INT, $int_options));
?>
But, what if I need to know if the elements of an array are in this range?(and probably remove those not) What is the best practice, considering the performance and memory usage. I prefer to use php functions instead of writing mine.
Slightly functional approach (I don't know if PHP support lambdas):
function mapper($n) { return $n >= 1 && $n <= 100 ? 1 : 0; }
...
if (array_product(array_map('mapper', $array)) == 1) { }
Not very performance nor memory efficient, though.
For removing, I'd suggest using array_filter.
function my_filter($n) { return $n >= 1 && $n <= 100; }
...
$newarray = array_filter($array, 'my_filter');
(btw, who the hell designed that language.. array_map and array_filter have different order of parameters?!)
If I were you, I will use a more simple approach:
$size=50;
if($size <= 100 && $size >= 1) {
return true;
} else {
return false;
}
No function call, simple integer comparison. Good performance.