PHP: array_uintersect() unexpected behaviour

99封情书 提交于 2019-12-10 19:08:11

问题


Test script

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

Actual Result

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
)

Expected Result

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

In other words, what I am expecting to be passed to the callback is the current element of the left array, and the current element of the right array.

Furthermore, I would expect the same logic to apply if I were to pass an additional array to array_uintersect - one more argument being passed to the callback ($c, for example).

Can someone explain this behaviour?


回答1:


What's not mentioned in the array_uintersect docs is that, internally, PHP sorts all the arrays first, left to right. Only after the arrays are sorted does PHP walk them (again, left to right) to find the intersection.

The third argument (the comparison function) is passed to the internal sort algorithm, not the intersecting algorithm. Thus, the debugging output seen is the sorting algorithm figuring out the ordering.

The zend_sort implementation generally uses a uses a bisecting quick sort implementation. For arrays of the size in your example, PHP uses insertion sort. For large arrays, PHP uses a 3 or 5 point pivot so as to improve worst-case complexity.

Since you're not explicitly returning any value from the comparison function, PHP defaults to returning null (0), and since PHP is using insertion sort, you're seeing O(n*n) behavior as the sort walks all the combinations.




回答2:


I have no idea why do you expect anything from the comparison callback, except comparing the values of the arrays. The sole purpose of the callback is to compare the next pair of items from both arrays.

The function returns the result of intersection of the two arrays. In the callback you express your idea of how the values are supposed to be compared. For example, the following code assumes that the intersection should be performed by comparing the first characters of the strings:

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

print_r($a);

Output

Array
(
    [1] => bar
)

The order of the items passed to the callback is specified by the PHP internals, and may easily change in future.

So the comparison function is not supposed to do anything, except comparing two variables. There is not even a hint of use of the callback for any other purpose in the official documentation.




回答3:


I believe the first two calls are being used to seed variables in the internal algorithm. But since you don't return anything that the algorithm can use to determine equality/sorting, it only runs the next two.

If you actually return 0, 1 or -1 then you see the full comparison chain that is needed to calculate the intersection:

$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;
});

Yields:

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:


I think you are looking for this ;)

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

This will output

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

Update: It returns the result you want with the array_uintersect method. It isn't the most efficient way to do this and didn't test it with different data sets etc but should work.

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

and the 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)
  }
}



回答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)


来源:https://stackoverflow.com/questions/40288922/php-array-uintersect-unexpected-behaviour

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