【问题标题】:Why does array_diff_uassoc compares keys whose value do not match为什么array_diff_uassoc比较值不匹配的键
【发布时间】:2015-06-07 21:46:59
【问题描述】:

我刚刚阅读了 that question 关于奇怪 php 行为的信息,尽管我可以进行更多研究,但我离理解它还差得很远。

我假设读者已经阅读了原始问题并且知道 OP 的代码块和示例,但简而言之,OP 正在尝试比较这两个数组,虽然结果很好,但比较函数似乎被不规律地调用:

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

The documentation 有点晦涩......但它确实声明:

如果第一个参数被认为分别小于、等于或大于第二个参数,则比较函数必须返回一个小于、等于或大于零的整数。 p>

据我了解,这意味着compare() 函数应该更像这样:

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

然而这仍然给出了非常奇怪的结果,甚至更多的“重复”

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

面对很多疑惑,我read the compat php function,实际发生检查的部分很有趣:

foreach ($args[0] as $k => $v) {
    for ($i = 1; $i < $array_count; $i++) {
        foreach ($args[$i] as $kk => $vv) {
            if ($v == $vv) { // compare keys only if value are the same
                $compare = call_user_func_array($compare_func, array($k, $kk));
                if ($compare == 0) {
                    continue 3; // value should not be added to the result
                }
            }
        }
    }
    $result[$k] = $v;
}

here's the actual source(每个comment

这段代码执行比较函数的方式不应该是输出我们看到的结果。 Foreach 无法在键中来回移动(AFAIK ???),这里的第一个键的顺序似乎就是这种情况:

1:2 3:1 2:1

此外,如果值不匹配,它不应该检查键,那么为什么要检查所有这些:

1:2 3:1 2:1 3 : 2 等等...

源码中最顶层的foreach()如何通过key来回循环?!

为什么仍然比较值不匹配的键?

即使是continued,foreach 循环是否真的会继续执行?

这是一个并发的例子吗?可以以某种方式启动 call_user_func_array 并实际执行比较函数的echo("$a : $b&lt;br/&gt;");,而不是按照它们“启动”的顺序??

【问题讨论】:

  • 从自定义比较函数要求你返回-1来看; 0;或 1,它似乎在两个数组之间进行比较之前或同时进行排序。
  • 我似乎无法理解它会在哪里这样做......

标签: php arrays


【解决方案1】:

我相信你已经查明了一个错误,我的朋友。我刚刚运行了您引用的问题中的代码,果然,它比较了不同值的键。但是,我想测试源代码本身是否包含错误,所以我将array_diff_uassoc this 的官方源添加到他的代码顶部,在我自己的命名空间内:

<?php

namespace mine;

// Code obtained from https://pear.php.net/reference/PHP_Compat-latest/__filesource/fsource_PHP_Compat__PHP_Compat-1.6.0a3CompatFunctionarray_diff_uassoc.php.html

function array_diff_uassoc()


{

    // Sanity check

    $args = func_get_args();

    if (count($args) < 3) {

        user_error('Wrong parameter count for array_diff_uassoc()', E_USER_WARNING);

        return;

    }



    // Get compare function

    $compare_func = array_pop($args);

    if (!is_callable($compare_func)) {

        if (is_array($compare_func)) {

            $compare_func = $compare_func[0] . '::' . $compare_func[1];

        }

        user_error('array_diff_uassoc() Not a valid callback ' .

            $compare_func, E_USER_WARNING);

        return;

    }



    // Check arrays

    $array_count = count($args);

    for ($i = 0; $i !== $array_count; $i++) {

        if (!is_array($args[$i])) {

            user_error('array_diff_uassoc() Argument #' .

                ($i + 1) . ' is not an array', E_USER_WARNING);

            return;

        }

    }



    // Compare entries

    $result = array();

    foreach ($args[0] as $k => $v) {

        for ($i = 1; $i < $array_count; $i++) {

            foreach ($args[$i] as $kk => $vv) {

                if ($v == $vv) {

               //   echo ("$v\n");
                    // echo ("$vv\n");
               //   echo ("$k\n");
                    // echo ("$kk\n");
                    // die();

                    $compare = call_user_func_array($compare_func, array($k, $kk));

                    if ($compare == 0) {

                        continue 3;

                    }

                }

            }

        }



        $result[$k] = $v;

    }

    return $result;

}

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\n");
    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, 'mine\compare');

这一次,它只比较值相等的键:

1 : 0
2 : 0
3 : 0

很奇怪吧?

【讨论】:

  • 我不确定您在此处发布的代码,但 PHP 的各种 array_diff 函数的代码是 here,并且是用 C++ 编写的。
  • @Powerlord 这段代码来自PEAR reference site,这是应该反映实际函数行为的 PHP 代码(没错,它是用 C 编写的)。在我的书中,C 代码的行为与记录的 PHP 版本不同的事实是一个错误。
  • PHP 中的兼容性函数与用 C 编写的原始函数具有相同的 API 和行为。这就是 PHP_Compat 包声明的所有内容PHP_Compat provides drop-in functions and constants for compatibility with newer versions of PHP, environment emulation, and an API to allow for version independent authoring. 所以我不明白你怎么能称它为漏洞。为什么你首先要在 compare 函数中使用 echo
  • @Pevara 指出调用回调函数与array_diff_uassoc() 中进行的测试无关的事实??
  • 行为可能不是正确的词。我的意思是 API 是一样的。它们都接受相同的输入参数并返回相同的输出。这就是你应该关心的全部。 C 版本和 PHP_Compat 版本这两个函数是可以互换的。你永远不应该依赖函数的内部运作,那只是一个等待发生的错误。你不能声称Evidently the compare function should be called three times in our case,你不应该关心
【解决方案2】:

来自commentpowerlord

从自定义比较函数要求你返回-1来看; 0;或 1,它似乎在两个数组之间进行比较之前或同时进行排序。

我被鼓励去阅读actual php_array_diff() sourcethe registered function for array_diff_uassoc(),并发现它多次使用比较功能,我错误地将其解释为 foreach 来回通过键。


源码中最顶层的foreach()如何通过key来回循环?!

没有。只是用户提供的函数

diff_data_compare_func = php_array_user_compare;

多次用于对数据进行排序和评估。

[...]
zend_sort((void *) lists[i], hash->nNumOfElements,
            sizeof(Bucket), diff_data_compare_func, (swap_func_t)zend_hash_bucket_swap);
[...]
while (Z_TYPE(ptrs[i]->val) != IS_UNDEF && (0 < (c = diff_data_compare_func(ptrs[0], ptrs[i])))) {
[...]
if (diff_data_compare_func(ptrs[0], ptr) != 0) {
[...]

为什么仍然比较值不匹配的键?

确实,问题中发布的 compat pear 代码暗示如果值不匹配,则甚至不应该运行比较函数,但 php_array_diff() 的行为不同。


foreach 循环实际上是否继续执行,即使它们已经继续执行? 这是并发的例子吗?

废话。如果我是你,我会把它删掉。

【讨论】:

    猜你喜欢
    • 2022-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多