为什么array_uintersect()会比较数组1和数组2、数组1和数组1、数组2和数组2之间的元素?

5

测试脚本

$i = 0;
array_uintersect(['foo', 'bar'], ['baz', 'qux'], function($a, $b) use (&$i) {
    print_r([$a, $b, $i++]);
});

实际结果

Array
(
    [0] => bar
    [1] => foo
    [2] => 0
)
Array
(
    [0] => qux
    [1] => baz
    [2] => 1
)
Array
(
    [0] => bar
    [1] => qux
    [2] => 2
)
Array
(
    [0] => bar
    [1] => foo
    [2] => 3
)

预期结果

Array
(
    [0] => foo
    [1] => baz
    [2] => 0
)
Array
(
    [0] => bar
    [1] => qux
    [2] => 1
)

换句话说,我期望传递给回调函数的是左侧数组当前元素和右侧数组当前元素。此外,如果我再向array_uintersect传递一个额外的数组,我也期望同样的逻辑应用于回调函数中(例如,传递$c作为参数)。有人能解释一下这个行为吗?

@mistermartin 我正在使用它进行调试,以便跟踪迭代发生的次数。 - The Onin
为什么不直接循环第一个数组并使用相同的索引从第二个数组中获取值呢? - Sander Visser
是的,但 array_uintersect 尝试交集值而不是键。http://php.net/manual/en/function.array-intersect-key.php - Sander Visser
PHP函数命名存在不一致性,但我找到了 ;) php.net/manual/en/function.array-intersect-ukey.php 但那不是你想要的。请看我的答案,其中包含array_map函数。 - Sander Visser
@AbraCadaver 我只对函数的行为感兴趣。 - The Onin
显示剩余3条评论
5个回答

6

array_uintersect文档没有提到的是,内部 PHP先对所有数组进行排序,从左到右。只有在排序后,PHP才会再次遍历它们(仍然是从左到右)以查找交集。

第三个参数(比较函数)被传递给内部排序算法,而不是交集算法。因此,看到的调试输出是排序算法确定顺序。

zend_sort实现通常使用二分快速排序实现。对于你的示例中的大小的数组,PHP使用插入排序。对于大型数组,PHP使用3或5点枢轴来提高最坏情况下的复杂度

由于您没有从比较函数中明确返回任何值,PHP默认返回null(0),并且由于PHP使用插入排序,因此当排序遍历所有组合时,您会看到O(n*n)的行为。


1
很棒的答案,这就是我一直在等待的答案。其他人试图提供解决方法并通过提供变通方法来获得声誉,但我的问题是为什么它会以这种方式行事。我查看了您链接的C代码,并且对我来说,似乎内部上array_uintersect(一般情况下为array_intersect)类似于使用foreach手动迭代数组并使用in_array()检查针元素是否存在于堆栈数组中,主要区别在于C代码在迭代之前对其进行排序,从而增加了查找速度。 - The Onin

4

我不知道为什么你希望从比较回调函数中期望任何东西,除了比较数组的值。回调函数的唯一目的是比较来自两个数组的下一个项目。

该函数返回两个数组的交集结果。在回调函数中,您可以表达您对值应该如何进行比较的想法。例如,以下代码假定应通过比较字符串的第一个字符来执行交集:

$a = array_uintersect(['foo', 'bar'], ['baz', 'qux'], function($a, $b) {
  return strcmp($a[0], $b[0]);
});

print_r($a);

输出

Array
(
    [1] => bar
)

回调函数中传递的项目顺序由PHP内部指定,未来可能会轻松更改。

因此,比较函数不应该做任何事情,除了比较两个变量。在官方文档中甚至没有暗示使用回调进行任何其他目的。


2
我认为前两个调用是用于在内部算法中设置变量。但由于您没有返回任何算法可以用来确定相等性/排序的内容,它只运行下面的两个调用。
如果您实际返回0、1或-1,则可以看到需要计算交集的完整比较链:
$i = 0;
array_uintersect(['foo', 'bar'], ['baz', 'qux'], function($a, $b) use (&$i) {
    print_r([$a, $b, $i++]);

    if ($a === $b) return 0;
    if ($a  >  $b) return 1;
    return -1;
});

产出:

Array
(
    [0] => bar
    [1] => foo
    [2] => 0
)
Array
(
    [0] => qux
    [1] => baz
    [2] => 1
)
Array
(
    [0] => bar
    [1] => baz
    [2] => 2
)
Array
(
    [0] => foo
    [1] => baz
    [2] => 3
)
Array
(
    [0] => foo
    [1] => baz
    [2] => 4
)
Array
(
    [0] => foo
    [1] => qux
    [2] => 5
)

0

我想你正在寻找这个;)

$result = array_map(function($a, $b) {
    return [$a, $b];
}, ['foo', 'bar'], ['baz', 'qux']);
var_dump($result);

这将输出

array(2) {
  [0]=>
  array(2) {
    [0]=>
    string(3) "foo"
    [1]=>
    string(3) "baz"
  }
  [1]=>
  array(2) {
    [0]=>
    string(3) "bar"
    [1]=>
    string(3) "qux"
  }
}

更新:使用array_uintersect方法可以返回您想要的结果。这不是最有效的方法,并且没有使用不同的数据集进行测试,但应该可以工作。
$entities = [
    [
        'id' => 1,
        'timestamp' => 1234
    ],
    [
        'id' => 2,
        'timestamp' => 12345
    ],
    [
        'id' => 3,
        'timestamp' => 123456
    ],
    [
        'id' => 8,
        'timestamp' => 123456
    ],
    [
        'id' => 10,
        'timestamp' => 123456
    ],
    [
        'id' => 11,
        'timestamp' => 123456
    ],
    [
        'id' => 12,
        'timestamp' => 123456
    ]
];

$identities = [1, 11, 2, 8, 10];

$result = array_uintersect($entities, $identities, function($a, $b) {

    // Both array skip
    if (is_array($a) && is_array($b)) {
        if ($a['id'] > $b['id']) {
            return 1;
        }
        return -1;
    }

    // Both int skip
    if (is_int($a) && is_int($b)) {
        if ($a > $b) {
            return 1;
        }
        return -1;
    }

    // $a is array
    if (is_array($a)) {
        if ($a['id'] == $b) {
            return 0;
        }
        elseif ($a['id'] > $b) {
            return 1;
        }
        return -1;
    }

    // $b is array
    if($b['id'] == $a) {
        return 0;
    }
    if($a > $b['id']) {
        return 1;
    }

    return -1;
});
var_dump($result);

以及结果

array(5) {
  [0]=>
  array(2) {
    ["id"]=>
    int(1)
    ["timestamp"]=>
    int(1234)
  }
  [1]=>
  array(2) {
    ["id"]=>
    int(2)
    ["timestamp"]=>
    int(12345)
  }
  [3]=>
  array(2) {
    ["id"]=>
    int(8)
    ["timestamp"]=>
    int(123456)
  }
  [4]=>
  array(2) {
    ["id"]=>
    int(10)
    ["timestamp"]=>
    int(123456)
  }
  [5]=>
  array(2) {
    ["id"]=>
    int(11)
    ["timestamp"]=>
    int(123456)
  }
}

这确实是我想要实现的目标,但我想使用array_uintersect函数,因为函数名中包含"intersect"(交集)一词(希望尽可能清楚地表达我的意图)。 - The Onin
我想要比较值,而不是键。具体来说,我有一个多维数组,每个元素都有两个子元素(id和时间戳),还有一个单维数组(只有id),我的目标是丢弃所有不包含单维数组元素的多维数组条目。 - The Onin
@NinoŠkopac,所以你想比较值,还是只是按照这个答案的方式重新排列数组?为什么要尝试执行交集,当你的数组交集为空时?你正在打印算法实现的一些隐藏细节,而不是函数结果。 - Max Zuber
我做到了!不确定是否稳定 :P 请查看更新的答案。请注意,这不是最有效的方法,但它使用了交集方法 ;) - Sander Visser
已更新并修正返回值 ;) 现在应该非常稳定了。 - Sander Visser
显示剩余4条评论

-4
<?php
    $i  = 0;
    $r1 = ['foo', 'bar'];
    $r2 = ['baz', 'qux'];
    $result = array_uintersect($r1, $r2, function($a, $b){
        return ($a[0]> $b[0]);
    });


    var_dump($result);
    // YIELDS::
    array (size=2)
      0 => string 'foo' (length=3)
      1 => string 'bar' (length=3)

3
需要解释一下。 - The Onin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接