对象数组的数组交集

Array intersection for array of objects

我有一个对象数组数组,我正在尝试获取在所有内部数组中找到的对象的交集。我的第一次尝试是在外部数组上使用扩展运算符并将其传递给 array_intersect:

array_intersect(...array_values($test))

但是,函数 array_intersect 使用基于字符串的比较 string representation:

Two elements are considered equal if and only if (string) $elem1 === (string) $elem2. In words: when the string representation is the same.

我可以轻松地在我的大多数对象上实现 __toString,但是有人已经将它用于其他目的,并且不能保证它是唯一的。如果我必须硬着头皮重构,我会的,但我只是看看是否有另一种方法可以彻底解决这个问题。

数组中的键无关紧要,外数组和内数组都是无界的。

(如果有人想知道,这是一堆组件的汇总搜索结果,如果有人想知道的话,它们会被放在一起)。

基本要点是这样的:

class sample
{
    public $id;

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

    // Comment this function out to see the error
    public function __toString()
    {
        return spl_object_hash($this);
    }
}

$obj1 = new sample('1');
$obj2 = new sample('2');
$obj3 = new sample('3');
$obj4 = new sample('4');
$obj5 = new sample('5');

$test = [
    'a' => [
        $obj1,
        $obj2,
        $obj5,
    ],
    'b' => [
        $obj2,
        $obj3,
        $obj5,
    ],
    'c' => [
        $obj2,
        $obj3,
        $obj5,
    ],
    'd' => [
        $obj2,
        $obj4,
        $obj5,
    ],
];

print_r(array_intersect(...array_values($test)));

运行 将产生我想要的结果:


Array
(
    [1] => sample Object
        (
            [id] => 2
        )

    [2] => sample Object
        (
            [id] => 5
        )

)

但是,如果您注释掉 __toString() 方法,您将看到异常 Object of class sample could not be converted to string

好吧,我写了这么久,以至于我的大脑能够转换思维,我自己想出了一个答案。不是很优雅,但仍然很干净,我不需要重构。

$final = array_shift($test);
while (count($test)) {
    $to_test = array_shift($test);
    $final = array_uintersect($final, $to_test, static function ($a, $b) {
        return spl_object_hash($a) <=> spl_object_hash($b);
    });
}

这只使用 array_uintersect 允许自定义比较函数,我们一次遍历每个数组并计算交集

嗯,你也可以实现自己的比较函数。

看看。

<?php declare(strict_types=1);

final class SampleComparator
{
    public static function compare(array $samples): array
    {
        if (!$firstElement = array_shift($samples)) {
            // If array is empty
            return [];
        }

        return self::compareWithSampleElements($firstElement, $samples);
    }

    private static function compareWithSampleElements(
        array $headSamples,
        array $tailListSamples
    ): array {
        $result = [];

        /** @var Sample $headSample */
        foreach ($headSamples as $headSample) {
            $result = self::iterateHeadSamples($tailListSamples, $headSample, $result);
        }

        return $result;
    }

    private static function iterateHeadSamples(
        array $tailListSamples,
        Sample $headSample,
        array $result
    ): array {
        /** @var array $tailSamples */
        foreach ($tailListSamples as $tailSamples) {
            $result = self::iterateTailSamples($tailSamples, $headSample, $result);
        }

        return $result;
    }

    private static function iterateTailSamples(
        array $tailSamples,
        Sample $headSample,
        array $result
    ): array {
        foreach ($tailSamples as $tailSample) {
            if ($headSample === $tailSample) {
                $result[$headSample->id] = $headSample;
                continue;
            }
        }

        return $result;
    }
}

用法:

$intersection = SampleComparator::compare($test);
print_r($intersection);
----------
*OUTPUT*

Array
(
    [2] => Sample Object
        (
            [id] => 2
        )

    [5] => Sample Object
        (
            [id] => 5
        )
)

当然还有测试。

<?php declare(strict_types=1);

final class SampleComparatorTest extends TestCase
{
    /**
     * @test
     * @dataProvider sampleProvider
     */
    public function sampleComparator(array $expected, array $actual): void
    {
        self::assertSame($expected, SampleComparator::compare($actual));
    }

    public function sampleProvider(): array
    {
        $obj1 = new Sample('1');
        $obj2 = new Sample('2');
        $obj3 = new Sample('3');
        $obj4 = new Sample('4');
        $obj5 = new Sample('5');

        return [
            'no values at all' => [
                [],
                []
            ],
            'no tail values' => [
                [],
                [
                    [$obj1, $obj2]
                ]
            ],
            'no same values' => [
                [],
                [
                    [$obj1, $obj2],
                    [$obj3, $obj4, $obj5]
                ]
            ],
            'repeat values' => [
                [1 => $obj1],
                [
                    [$obj1, $obj1],
                    [$obj1, $obj1, $obj1]
                ]
            ],
            'only 2 and 5' => [
                [2 => $obj2, 5 => $obj5],
                [
                    [$obj1, $obj2, $obj5],
                    [$obj2, $obj3, $obj5],
                    [$obj2, $obj3, $obj4],
                ]
            ],
        ];
    }
}