2

I'm trying to compute difference of two arrays of objects with array_udiff(). My object structure is complicated and I can not rely on quantitative properties, because those properties of objects in both arrays may have same values, but they are stored as different instances (it is expected behavior).

So, here is my question: is there any way to detect same instances in both arrays using reference detection?

What have I tried?

I've tried this:

<?php
header('Content-Type: text/plain; charset=utf-8');

$a = new stdClass;
$b = new stdClass;
$c = new stdClass;

$a->a = 123;
$b->b = 456;
$c->c = 789;

$x = [ $a, $c ];
$y = [ $b, $c ];

$func   = function(stdClass &$a, stdClass &$b){
    $replacer   = time();
    $buffer     = $a;

    $a = $replacer;

    $result = $a === $b;

    $a = $buffer;

    return $result;
};

$diff   = array_udiff($x, $y, $func);

print_r($diff);
?>

And got unsuccessful results, because if I try to replace value for $x element, php will not remove reference from $y.

I have same output for:

$func   = function(stdClass &$a, stdClass &$b){
    return $a === $b;
};

and for

$func   = function(stdClass &$a, stdClass &$b){
    return $a == $b;
};

It is an empty array.

Any suggestions?

Stéphane Bruckert
  • 21,706
  • 14
  • 92
  • 130
BlitZ
  • 12,038
  • 3
  • 49
  • 68
  • `$buffer = $a; ... $a = $buffer;` ==> $a doesn't really change, does it? sure it's assigned `$replacer`, but then it's reassigned back to its original value... and `$diff` will only contain `false` – Elias Van Ootegem Oct 15 '13 at 12:48
  • @EliasVanOotegem `$diff` actually contains an empty array. Check it. Also, why then `===` is not supplying `true`. – BlitZ Oct 15 '13 at 12:57
  • Becayse `$b` is an instance of `stdClass`, and you're comparing it to `$a`, after reassigning that reference an _int_ objects !== ints – Elias Van Ootegem Oct 15 '13 at 12:57
  • @EliasVanOotegem this thing worked once [here](http://stackoverflow.com/questions/15776880/reference-detection-in-array-from-another-function/16210223#16210223), so I just have checked. – BlitZ Oct 15 '13 at 12:59
  • Sure it worked there, but that code is comparing _primitives_, not objects... there's a big difference. BTW: I said in my answer, not to use the `&` operator (there's no need with objects, anyway), [here's why](http://stackoverflow.com/questions/17459521/what-is-better-in-a-foreach-loop-using-the-symbol-or-reassigning-based-on-k/18464019#18464019), you might want to read through that – Elias Van Ootegem Oct 15 '13 at 13:10

3 Answers3

1

Here's your problem, from the manual page:

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

You're returning a boolean (false), because you're comparing a timestamp to an object. (int) false is zero, so each and every object will be seen as equal, hence your $diff Array is empty.
Change your callback function to:

$func = function(stdClass $a, stdClass $b)
{//don't pass by reference, it's quite dangerous
    $replacer = time();
    $buffer = $a;
    $a = $replacer;
    $result = $a === $b ? 0 : -1;//equal? return zero!
    $a = $buffer;
    return $result;
};

That'll work, but $diff will now, obviously contain all objects. And your callback is a bit messy, consider:

$func = function(stdClass $a, stdClass $b)
{//don't pass by reference, it's quite dangerous
    return $a === $b ? 0 : -1;//compare objects
    //or, if you want to compare to time, still:
    return time() === $b ? 0 : -1;
};

That's a hell of a lot cleaner, and shorter, is it not?

Note
You will have to return -1 in case the two objects don't equate. Returning 1 in such cases implies that the object you're comparing is already greater than the values you're comparing it to. In that case, PHP will just stop looking, and will simply assume the value is not present in the array you're comparing the first array to... ok, this is getting rather complicated. Take your example:

[$a, $c] compare to [$b, $c]:
$a === $b => false: returns 1
    PHP assumes $a > $b, so $a > $c is implied, $a is pushed into $diff
$c === $b => false returns 1
   PHP assumes $c > $b, and is pushed to $diff, it's never compared to the next elem...

Returning -1 on false, however:

[$a, $c] compare to [$b, $c]:
$a === $b => false: returns -1
    PHP assumes $a < $b, so:
$a === $c => false: returns -1
    No more elems left, push $a to $diff
$c === $b => false returns -1
   PHP assumes $c < $b, moving on:
$c === $c => true returns 0
   Match found, $c is NOT pushed to $diff
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • http://codepad.viper-7.com/E60h70 ? Also, why doesn't it exclude `$c` instance from your answer? – BlitZ Oct 15 '13 at 13:08
  • @HAL9000: Because $x and $y _both_ have $c as one of its values. `array_udiff` iterates the first array, and compares _each_ value with _every single value in each and every one of the other array(s)_, if the value is not found in any of the other arrays, it's not returned, if it is, it _. In your case `$a` and `$c` are each mached against both `[ $b, $c ]`, `$c` is found, so it's not included, `$a` is not found, so `$diff` will contain `$a` – Elias Van Ootegem Oct 15 '13 at 13:15
  • @HAL9000: Updated my answer, too... forgot about the less-than/greater-than distinction... – Elias Van Ootegem Oct 15 '13 at 13:19
0

Despite, that I've accepted @Elias Van Ootegem answer, it will not help with any possible object combinations in diff array.

To detect references you may use === or !== comparison operators:

when using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

So here is the function, which accepts two arrays, but might be improved to accept more:

function array_exclude_instances(array $source, array $excludes){
    foreach($source as $index => $current){
        foreach($excludes as $exclude){
            if($exclude !== $current)continue 1;

            unset($source[$index]);

            continue 2;
        }
    }

    return array_values($source);
}

Test:

$a = new stdClass;
$b = new stdClass;
$c = new stdClass;

$a->x = 123;
$b->x = 123;
$c->x = 456;

$x = [$a, $c];
$y = [$c, $b];

$result = array_exclude_instances($x, $y);

Results:

Array
(
    [0] => stdClass Object
        (
            [x] => 123
        )

)

Online test @ 3v4l.

BlitZ
  • 12,038
  • 3
  • 49
  • 68
-1

what happens if you replace:

$func   = function(stdClass &$a, stdClass &$b)

to:

function func(stdClass &$a, stdClass &$b)

and call like this:

$diff   = array_udiff($x, $y, 'func');
Adam
  • 1,371
  • 2
  • 11
  • 12