comparing arrays in PHP - interesting behaviour - php

The first example:
$x = array("a" => 1, "b" => 2);
$y = array("b" => 1, "a" => 2);
$xLessY = ($x < $y);
$xGreaterY = ($x > $y);
var_dump($xLessY, $xGreaterY);
Result: $xLessY = true, $xGreaterY = true
The second example:
$x = array("a" => 2, "b" => 1);
$y = array("b" => 2, "a" => 1);
$xLessY = ($x < $y);
$xGreaterY = ($x > $y);
var_dump($xLessY, $xGreaterY);
Result: $xLessY = false, $xGreaterY = false
According to documentation on http://docs.php.net/manual/en/language.operators.comparison.php:
if key from operand 1 is not found in
operand 2 then arrays are
uncomparable, otherwise - compare
value by value
In our case each key from array $x is present in array $y, so $x and $y are comparable.
See also the example from documentation:
// Arrays are compared like this with standard comparison operators
function standard_array_compare($op1, $op2)
{
if (count($op1) < count($op2)) {
return -1; // $op1 < $op2
} elseif (count($op1) > count($op2)) {
return 1; // $op1 > $op2
}
foreach ($op1 as $key => $val) {
if (!array_key_exists($key, $op2)) {
return null; // uncomparable
} elseif ($val < $op2[$key]) {
return -1;
} elseif ($val > $op2[$key]) {
return 1;
}
}
return 0; // $op1 == $op2
}
This behaviour is really strange: $x is less than $y and at the same time $x is greater than $y (the first example) and two arrays are comparable.
I think this is because php always compares starting from the one definite side of sign '<'. I mean: for ($x < $y) php takes $x as operand 1, for ($x > $y) it takes $y as operand 1. Although I didn't find anything about this behaviour in documentation.
What are your thoughts on this?

Your assumption is correct. The > operator is parsed as
| expr '>' expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$3, &$1 TSRMLS_CC); }
This basically says, X > Y is equivalent to not X < Y, which is of course wrong when the comparison is not commutative. Consider reporting this on bugs.php.net.

I wouldn't say the bug is in $x > $y being substituted for $y < $x.
Sure, if you implemented $x > $y in a way that the arguments did not exchange positions when passed to the comparison function, you would solve this particular problem. But you get another in return.
Right now you have:
$x < $y <=> cmp($x, $y) == -1
$x > $y <=> cmp($y, $x) == -1
Because the first key of the first argument is always compared first, both conditions are true if reset($x) < $y[key($x)] and reset($y) < $x[key($y)].
But consider another implementation, which would solve this problem:
$x < $y <=> cmp($x, $y) == -1
$x > $y <=> cmp($x, $y) == +1
Now < and > are consistent when the order of the operands is fixed, but we now get weird behavior when we swap the operands because we could still have cmp($x, $y) == -1 and cmp($y, $x) == -1, which would mean $x < $y and $y < $x would both be true.
In sum, the only solution would be to fix the comparison function so that its behavior was antisymmetric, i.e. so that cmp($x, $y) == - cmp($y, $x), at least within a set of elements that are claimed to be comparable.

I may be wrong but I don't think you can compare arrays that way. I always assumed one can check for equality or inequality, but not compare quantities with < and >.
The man page on array operators seems to confirm this.

Related

PHP IF-ELSE STAMENT

Code:
<?php
$a = 200;
$b = 300;
if ($a > $b + $b != 3)
print "Correct";
else
print "Incorrect";
?>
output is: Correct
Can someone help me understand why the output became "Correct"?
To understand what is happening here, you need to look at the list of operator precendence to see what is being evaluated first. It's not left to right. The order of the operators in your if statement are as follows:
+ - ++ -- ~ (int) (float) (string) (array) (object) (bool) # - arithmetic (unary + and -), increment/decrement, bitwise, type casting and error control
< <= > >= - associative comparison
== != === !== <> <=> - non associative comparison
So in essence, your if statement breaks down to this:
(($a > ($b + $b)) != 3)
With your values becomes
((200 > (300 + 300)) != 3)
((200 > 600) != 3)
(false != 3)
So of course, false is not 3, and makes your if statement correct. If you want to evaluation 200 is greater than 300 AND 300 is not 3, then you need the logical AND operator, or &&, which would be
($a > $b && $b != 3)
which would print Incorrect

Test whenever a value x is greater by exactly 1 compared to a value y modulo 3

How do I check that x is 1 greater (ahead) of y in a modulo sense (mod 3)?
This is when x = 0 and y = 2, when x = 1, y = 0 and when x = 2, y = 1.
I tried testing like this:
php -a
php > $x = 0;
php > $y = 2;
php > echo ($x - $y) % 3;
-2
php > $x = 1;
php > $y = 0;
php > echo ($x - $y) % 3;
1
php > $x = 2;
php > $y = 1;
php > echo ($x - $y) % 3;
1
It is not working for the case where x = 0 and y = 2. How can I calculate this so that $x is 'ahead' of $y by 1 in a modulo sense?
I will first explain my understanding of your following sentence:
How do I check that x is 1 greater (ahead) of y in a modulo sense (mod 3)?
Following the examples provided, I assume you mean that, if 1 is added to $y, and we take the mod 3 of $y, we would get the mod 3 of $x.
With that in mind, we could write the following code, witch would return true if $x is "ahead" of $y by 1. (I hope you can abstract that example to whatever situation you are facing):
function check($x, $y, $mod) {
return $x % $mod == ($y + 1) % $mod;
}
//$x = 0 and $y = 2
echo check(0,2,3); //returns true
//$x = 1 and $y = 0
echo check(1,0,3); //returns true
//$x = 2 and $y = 1
echo check(2,1,3); //returns true
//$x = 0 and $y = 1
echo check(0,1,3); //returns false because $x is 2 "ahead" of $y
If you want a more generalized version of the function, with an arbitrary difference, you can use this (it should work with positive and negative differences):
function check($x, $y, $mod, $diff) {
return $x % $mod == ($y + $diff) % $mod;
}
Why is it that you used 0 for x and 2 for y? (Sorry, but I'm not much of an algebraist.)
Although your situations are pretty much clarified, it doesn't make sense (at least for me) that x would be 1 bit ahead of y when it is clearly 0 < 2.
As we know, the statement (0 - 2) % 3 would be -2 because the mod is 3, and 0 - 2 is -2, so the result is -2.
Mathematics can have some illogical sense (in my opinion) sometimes, so its worth noting that 0 isn't ahead of 2 (in the sense of programming variables) at all.
And to actually answer your question on calculation, you could have x = 0 and y = -1 as your variables instead, as per se the logic of your statement, then just increment by 1 for both variables, and the result is still the same.
Proof:
<?php
$x = 0
$y = -1
echo ($x - $y) % 3 // outputs 1
echo (++$x - ++$y) % 3 // still outputs 1
echo (++$x - ++$y) % 3 // also outputs 1
echo (++$x - ++$y) % 3 // yep, still the same
echo (++$x - ++$y) % 3 // alright, you get the deal
?>

List of numbers until n divisible by A or B but not divisible by C

I have three integers: A, B, C
I want to print all integers from 1 to range which are divisible by A or B but not by C.
My code
for($n=0; $n < $range; $n++){
if(($n < $a && $n < $b) || ($n % $c == 0)){
return [];
}
if(($n % $a == 0 || $n % $b == 0) && ($n % $c > 0)){
$outputArr[] = $n;
}
}
Is there any more efficient way to do this?
You can speed this up but it is more complicated, especially if you must print these out in order. Here is a method that doesn't print them out in order.
First write a greatest common divisor (gcd) function in PHP, and then write a least common multiple (lcm) function that uses the gcd function. Compute m = lcm(a, b). Iterate over multiples of a and print them out if they are not divisible by c. Next, iterate over multiples of b and print them out if they are not divisible by m or c.
Other optimizations along these lines are possible. For example, you can precompute the multiples of a or b that are not multiples of m and store them in an array. This works if m is not too large, division is more expensive than array access in PHP, and range is significantly larger than m.
PHP version 7 or higher is so fast when only integer operations are used that micro-optimizations are no longer needed.
$res = [];
$a = 9;
$b = 13;
$c = 26;
$range = 10000;
for($n=$a; $n <= $range; $n += $a){
if($n%$c != 0) $res[] = $n;
}
for($n=$b; $n <= $range; $n += $b){
if($n%$c != 0) $res[] = $n;
}
$res = array_unique($res);
sort($res);
This example takes about 1 millisecond to calculate the 1411 values on my 8-year-old system. This time for the presentation of the result is several times greater.
I would use range() and array_filter().
$range = 20;
$A = 2;
$B = 3;
$C = 9;
$nums = array_filter(range(1, $range), function ($x) use ($A, $B, $C) {
return (!($x % $A) || !($x % $B)) && $x % $C;
});
var_dump($nums);
Here is a more optimized solution, that also works efficient when a and b are large. You can simply run through the multiples of a and b:
for($na=$a, $nb=$b; $na <= $range || $nb <= $range; ){
if ($na <= $nb) {
if ($na % $c != 0)
$outputArr[] = $na;
if ($na == $nb)
$nb += $b;
$na += $a;
} else {
if ($nb % $c != 0)
$outputArr[] = $nb;
$nb += $b;
}
}
Each output number is only generated once, and already in the desired order.
If you are afraid the modulo test is slow, you could also have a next multiple of c running along, but that looks like too much overhead.

php to compare two lapses of time [duplicate]

This question already has answers here:
Determine Whether Two Date Ranges Overlap
(39 answers)
Closed 6 years ago.
I'm looking for a way to compare two lapses of time.
If the second lapse or part of it is in the first one -> return false.
Although, if the first one or a part of it is in the second one -> return false too.
I feel like I didn't have to skive math lessons...
I got
// first lapse
$a = strtotime('2016/05/04 22:50');
$b = strtotime('2016/05/20 22:15');
// second lapse
$y = strtotime('2016-05-12 12:00');
$z = strtotime('2016-05-20 10:00');
if (($y >= $a && $y <= $b) || ($z >= $a && $z <= $b)
|| ($a >= $y && $a <= $z) || ($b >= $y && $b <= $z))
return false;
But it's quiet confuse in my brain. I'm not sure it does what it supposes to do.
Thank you.
Edit
I found my question was duplicated with this one
if $a <= $b and $y <= $z, count
min($b, $z) - max($a, $y)
if < 0 - no overlap
= 0 - common boundary point
> 0 - overlap

Count from 3 in 3 until I reach 10 with php

I am trying a basic program to count from 3 in 3 until I reach 10. I tried:
<?php
$a = 0;
$b = 0;
while ($a < 10) {
$a += $b + 3;
echo "$a\n\r";
}
?>
The output is 3, 6, 9, 12. And the expected output would be 3, 6, 9. because I added < 10. Why is it doing this? Sorry for my noob question, but it's confusing: http://codepad.org/Y8yhd0JP
The value of $a is $a=9 in the 3rd iteration, so the loop will continue to go on to add upto 12.
To obtain the result as 3,6,9 check for while($a < 9){...}
<?php
$a = 0;
$b = 0;
while ($a < 9) {
$a += $b + 3;
echo "$a\n\r";
}
?>
This would be better to do with a do-while cycle, with $a initialised to its starting value before the cycle
<?php
$b = 0;
$a += $b + 3;
do {
echo "$a\n\r";
$a += $b + 3;
} while ($a < 10);
?>
The while will check if the value in $a is more than 10.
After the check you add to the value and print it, then check again, if its over 10, it will not do another loop.
So when it prints 12, it will have checked if $a was over 10, which is was not, cause it was 9. Then do the addition and echo and check again and exit.
To start with, note the $b is always 0, so you can eliminate it like this:
$a = 0;
while ($a < 10) {
$a += 3;
echo "$a\n\r";
}
Note that you change the value of $a between the test and the echo. That is probably the reason for your surprise.
A better way to write it would be
for ($a = 3; $a < 10; $a += 3) {
echo "$a\r\n";
}
An uglier solution:
$a = 0;
$b = 0;
while (($a += $b + 3) < 10)
echo "$a\n\r";
I moved the addition in the while loop. Here you dont't have to put the echo in {} because there's only one statement.
in your code you are printing the element after it is increased, so after it prints 9 , it checks the condition ($a < 10) , the condition is true so it proceeds and print 12 , while the next iteration the condition ($a < 10) fails and it stops. this is the reason , you can avoid it by changing your code as follows,
<?php
$a = 0;
$b = 0;
while ($a < 9) {
$a += $b + 3;
echo "$a\n\r";
}
?>
Hope it helps.

Categories