1

I have a custom callback in my array_uintersect() call because I need to case-sensitively compare strings in two elements while comparing rows between two multi-dimensional arrays.

function filterIdenticalEntriesCallback($a, $b) {
    echo "<tr><td>{$a['file']}</td><td>{$b['file']}</td><td>{$a['md5']}</td><td>{$b['md5']}</td></tr>";
    if ( (strcmp($a['file'], $b['file']) == 0 ) && ( strcmp($a['md5'],$b['md5']) == 0)){ 
        return 0;
    }
    return 1;
}

$vieja = array(
        array('file' => 'a', 'md5' => '1'),     //cambia
        array('file' => 'b', 'md5' => '2'),     //igual
        array('file' => 'c', 'md5' => '3'),     //igual
        array('file' => 'd', 'md5' => '4'),     //igual
);

$nueva = array(
        array('file' => 'a', 'md5' => '2'),     //cambia
        array('file' => 'b', 'md5' => '2'),     //igual
        array('file' => 'c', 'md5' => '3'),     //igual
        array('file' => 'd', 'md5' => '4'),     //igual
);

echo "<table>";
$ignorar = array_uintersect($nueva, $vieja, 'filterIdenticalEntriesCallback');
echo "</table>";

echo "<pre>";
print_r($ignorar);
echo "</pre>";
?>

OUTPUT

b   a   2   2
b   c   2   3
d   b   4   2
a   c   2   3
b   a   2   1
b   c   2   3
d   b   4   2
a   c   1   3
c   c   3   3
c   a   3   2
a   a   2   1
a   b   2   2
a   d   2   4
Array
(
    [2] => Array
        (
            [file] => c
            [md5] => 3
        )

)

I can't understand why this code doesn't produce the correct output which should be an array with "B", "C" and "D" elements, because only "A" element is different from $vieja to $nueva...

if I make both "A" elements equals the output is ok....

EDIT:

function filterIdenticalEntriesCallback($a, $b) {
    echo "<tr><td>{$a['file']}</td><td>{$b['file']}</td><td>{$a['md5']}</td><td>{$b['md5']}</td></tr>";
    if (strcmp($a['file'], $b['file']) == 0 ){ 
        if ( strcmp($a['md5'],$b['md5']) == 0)
            return 0;
        else return 1;
    } else return -1;
}

Using this callback is working ok, but I still don't understand why... what -1 and 1 callback results means to the function? I mean, I need only equal values... which are when callback return 0... I don't need other cases...

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
KnF
  • 166
  • 10
  • Your comparison function does only return two of the three possible return values. This might be causing your issue. – hakre Nov 18 '11 at 02:00
  • The documentation for the function explains the callback return values: http://php.net/array_uintersect – hakre Nov 18 '11 at 02:29

3 Answers3

3

The filter that works for me:

function filterIdenticalEntriesCallback($a, $b) {
        $cmp = strcmp($a['file'], $b['file']);
        if($cmp==0) {
                return strcmp($a['md5'], $b['md5']);
        } else {
                return $cmp;
        }
}

Because of the way intersect works internally, you must return a negative number if the first is less than the second, 0 if they are the same, and a positive number if the first is greater than the second. This has been observed before by nate.


A neater filter function:

function filterIdenticalEntriesCallback($a, $b) {
        if ($a==$b) {
                return 0;
        } else {
                return $a<$b ? -1 : 1;
        }
}

This works because of the way PHP does comparing. In theory, this comparing function would work for ANY two objects of the same type where you want to make sure all of the attributes and their values are the same.

Or the same thing as a one liner:

function filterIdenticalEntriesCallback($a, $b) {
        return $a == $b ? 0 : ($a < $b ? -1 : 1);
}
Levi Morrison
  • 19,116
  • 7
  • 65
  • 85
  • but, $a and $b are two arrays... is it possible to compare two arrays this way? Lets see if I understand correctly what you say: PHP is comparing the data inside the arrays in binary mode? obviously ignoring the array keys.. – KnF Nov 18 '11 at 07:36
  • Looks like you're right... the onelined callback works like a charm ;) So... a new question raises... isn't this the same that array_intersect?? :P edit: I'll answer myself... It isn't the same, becuase it doesn't work haha! :) Thanks Levi! – KnF Nov 18 '11 at 07:43
  • @KnF Yeah, it's not the same, because `array_intersect` compares values, whereas doing `==` will compare attributes and their values, in the case of an array it's their keys and values. – Levi Morrison Nov 18 '11 at 17:43
  • But why does the comparison work this way (needs to return -1, 0, or 1 instead of boolean)? – Kevin C. Nov 10 '12 at 01:54
  • @KevinC. Internally it must perform a sort operation. It's just part of the internal workings. – Levi Morrison Nov 16 '12 at 20:36
2

A comment in the PHP documentation says that you can't just return 0 and 1, but you must also have cases to return -1 when appropriate. You might have better luck if you have a case to return -1 as well.

jprofitt
  • 10,874
  • 4
  • 36
  • 46
0

Yes, array_uintersect() requires 3-way comparisons to be returned by the callback because its performance optimization relies on it.

Because you are enforcing case-sensitive comparisons on the subarray elements in the order which they appear in all subarrays AND because all subarrays have the exact same keys, you can make a function-less 3-way evaluation using the "spaceship operator".

Code: (Demo)

var_export(
    array_uintersect(
        $nueva,
        $vieja,
        fn($a, $b) => $a <=> $b
    )
);

If the order of elements in the rows are inconsistent, you could rely on:

fn($a, $b) => [$a['file'], $a['md5']] <=> [$b['file'], $b['md5']]
//             ^^rule 1^^  ^^rule 2^       ^^rule 1^^  ^^rule 2^

or

fn($a, $b) => $a['file'] <=> $b['file'] ?: $a['md5'] <=> $b['md5']
//            ^^^^rule 1 comparison^^^^    ^^tie breaking rule 2^^

but I usually only use multiple spaceship operators if I am using function calls in subsequent tie-breakers (as a performance optimization).

mickmackusa
  • 43,625
  • 12
  • 83
  • 136