Behavior of array_diff_uassoc not clear

六眼飞鱼酱① 提交于 2019-12-10 14:53:53

问题


First of all I need to mention that I digged into manual and php docs and didnt find an answer. Here's a code I use:

class chomik {

    public $state = 'normal';
    public $name = 'no name';

    public function __construct($name) {
        $this->name = $name;
    }

    public function __toString() {
        return $this->name . " - " . $this->state;
    }
}

function compare($a, $b) {
    echo("$a : $b<br/>");
    if($a != $b) {
        return 0;
    }
    else return 1;
}

$chomik = new chomik('a');
$a = array(5, $chomik, $chomik, $chomik);
$b = array($chomik, 'b', 'c', 'd');
array_diff_uassoc($a, $b, 'compare');

What I thought, array_diff_uassoc will compare all values of these two arrays, and if values exists, then will run key comparison. And the output of this code is:

1 : 0
3 : 1
2 : 1
3 : 2
1 : 0
3 : 1
2 : 1
3 : 2
3 : 3
3 : 2
2 : 3
1 : 3
0 : 3

So first of all why some pairs (1 : 0 or 3 : 1) are duplicated? Does it mean function forgot that it already compared this items? I thought that it will compare all equal-by-value pairs, but I dont see it in output. Am I missing something?

So question is: what is exact behavior of this function in terms of order of comparison, and why I see this duplicates? (my PHP version, if it helps is: PHP Version 5.3.6-13ubuntu3.6)

I'm really confused, and waiting for some good explanation of it...


回答1:


I think you missed the return value section.

Returns an array containing all the entries from array1 that are not present in any of the other arrays.

the array keys are used in the comparison.

What is missing in the text is that the comparison is only done associatively. This means that any automatically declared or user defined numerical keys are typed as strings not integers.

So with

$one = array(a,b,c,'hot'=>d); // d has no match and  will be returned as array and go to the function alone
$two = array(a,b,c,d,e,f); //

Because $one hot=>d does not match $two 0=>d on an associative level $one hot=>d is returned.

Because of the PHP quirk of string and integer data type comparisons a user defined function can be used to enhance the comparison by using stronger comparison operations like ===.

This helps in situations where the type is ambiguous '0'=>d and 0=>d might look similar but are not until you say so in your code.

Luckily type hinting is coming to PHP7 to rid us of this type of weird construct and unclear documentation.

I am adding this from my comment because it pertains to your understanding of which php constructs are best used in your case. My comment:

I am not so sure about that since if($a != $b) { in their code is a problem. Since they are mistakenly using equality when they should be using identical operators !==. And they are using numerical keys in a construct designed for associative keys. they are probably also unaware of array_udiff which a better match for the data involved




回答2:


This is somewhat intriguing indeed. I looked up the latest source of PHP on github (which is written in C++ as you probably know) and tried to make sense of it. (https://github.com/php/php-src/blob/master/ext/standard/array.c)

A quick search showed me that the function in question is declared on line 4308

PHP_FUNCTION(array_diff_uassoc)
{
    php_array_diff(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIFF_ASSOC, DIFF_COMP_DATA_INTERNAL, DIFF_COMP_KEY_USER);
}

So that shows that the actual work is done by the php_array_diff function, that can be found in that same file on line 3938. It's a bit long to paste it here, 265 lines to be exact, but you can look it up if you want.

That is the point where I gave up. I have no experience in C whatsoever, and it is to late and I'm to tired to try and make sense of it. I suppose key comparison is done first, as it is probably more performant then comparing the values, but that is just a guess. Anyway, there is probably a good reason why they do it the way they do.

All that is just a long introduction to say, why would you want to put an echo inside your compare function in the first place? The goal of array_diff_uassoc is the output of the function. You should not rely on how the parser handles it. If they decide tomorrow to change the internal workings of that C function to ie. do the value comparison first, you'll get an entirely different result.

Perhaps you could use this replacement function that is written in php: http://pear.php.net/reference/PHP_Compat-1.6.0a2/__filesource/fsource_PHP_Compat__PHP_Compat-1.6.0a2CompatFunctionarray_diff_uassoc.php.html

That way you can rely on the behaviour to not change, and you have full control of the internal workings...




回答3:


from op's comment that

I want only these elements which are not in second array ($a[0])

can't you use array_diff($a, $b);? it returns

array(1) {
  [0]=>
  int(5)
}

otherwise,

The documentation states that:

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.

As I understand it, that means that the compare() function should be more like this:

function compare($a, $b) {
    echo("$a : $b<br/>");
    if($a === $b) return 0;
    else if ($a > $b) return 1;
    else return -1;
}

However even with this correction, it has very strange compare results:

1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
0 : 0
1 : 0
1 : 1
2 : 0
2 : 1
2 : 2
3 : 0
3 : 1
3 : 2
3 : 3

I asked another question about this as it was getting out of the scope of an answer.



来源:https://stackoverflow.com/questions/9458652/behavior-of-array-diff-uassoc-not-clear

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!