Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

comparing arrays in PHP - interesting behaviour

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?

like image 294
Andy Avatar asked Jun 25 '11 20:06

Andy


2 Answers

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.

like image 50
user187291 Avatar answered Sep 28 '22 07:09

user187291


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.

like image 29
Artefacto Avatar answered Sep 28 '22 06:09

Artefacto