【问题标题】:Why does array_uintersect() compare elements between array1 & array2, array1 & array1, and array2 & array2?PHP:array_uintersect() 意外行为
【发布时间】:2017-03-10 09:25:10
【问题描述】:

测试脚本

$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)。

有人可以解释这种行为吗?

【问题讨论】:

  • 我不明白你在这里使用$i。来自文档:“如果第一个参数分别被认为小于、等于或大于第二个参数,则比较函数必须返回一个小于、等于或大于零的整数。”
  • @mistermartin 我将它用于调试目的;一种跟踪迭代发生次数的方法。
  • 为什么不直接遍历第一个数组并使用相同的索引从第二个数组中获取值?
  • @SanderVisser 当然可以,但我想使用这个功能,以便将来接手我工作的任何开发人员立即知道发生了什么。交点是不言自明的。
  • 是的,但是 array_uintersect 试图与值相交而不是键 php.net/manual/en/function.array-intersect-key.php

标签: php arrays set-intersection array-intersect


【解决方案1】:

array_uintersect docs 中没有提到的是,在内部,PHP sorts all the arrays 首先是从左到右。只有数组排序之后,PHP 才会遍历它们(同样,从左到右)以找到交点。

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

zend_sort implementation 一般是uses a bisecting quick sort implementation。对于您示例中大小的数组,PHP 使用插入排序。对于大型数组,PHP 使用 3 点或 5 点枢轴,以便 improve worst-case complexity

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

【讨论】:

  • 很好的答案,这是我一直在等待的答案。其他人试图通过提供变通方法来提供帮助并获得一些声誉,但我的问题是为什么它会这样做。我浏览了您链接的 C 代码,在我看来,在内部,array_uintersect(一般为array_intersect)类似于使用foreach 手动迭代数组并检查其中是否存在针元素in_array() 的 haystack 数组,主要区别在于 C 代码在迭代之前对其进行排序,从而提高查找速度。
【解决方案2】:

除了比较数组的值之外,我不知道您为什么对比较回调有任何期望。回调的唯一目的是比较两个数组中的下一对项目。

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

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

print_r($a);

输出

Array
(
    [1] => bar
)

传递给回调的项目的顺序是由 PHP 内部指定的,将来可能很容易改变。

所以比较函数不应该做任何事情,除了比较两个变量。官方文档中甚至没有暗示将回调用于任何其他目的。

【讨论】:

    【解决方案3】:

    我相信前两个调用被用于在内部算法中播种变量。但由于您没有返回任何算法可用于确定相等/排序的内容,因此它只运行接下来的两个。

    如果您实际返回 01-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
    )
    

    【讨论】:

      【解决方案4】:

      我想你正在寻找这个 ;)

      $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 词(想尽可能清楚地说明我在做什么) .
      • 来自文档array_uintersect — Computes the intersection of arrays, compares data by a callback function 所以我认为array_uintersect 不太清楚,因为您没有比较“数据”的文档。 array_intersect_ukey 做你想做的事,但不返回你想要的结果。它不结合值。
      • 我确实想比较值,而不是键。具体来说,我有一个多维数组,每个元素都有两个子元素(id 和时间戳),还有一个单维数组(只有 id),我的目标是丢弃不包含元素的多维数组中的所有条目来自一维数组。
      • @NinoŠkopac 所以你想比较值,或者只是按照这个答案重新排列数组?当您的数组交集为空时,为什么要尝试执行相交?您正在打印算法实现的一些隐藏细节,而不是函数结果
      • 好的,我想我现在明白了,所以你有一个数组,其中包含具有id 和其他一些属性的对象,并且你有一个包含ids 的数组,并且你想过滤所有对象'未在包含 ids 的数组中定义
      【解决方案5】:
      <?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)
      

      【讨论】:

      • 可以解释一下。
      猜你喜欢
      • 1970-01-01
      • 2019-05-01
      • 2011-07-15
      • 2011-04-27
      • 2019-12-07
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多