对象的 array_unique ?

108

有没有像array_unique一样的方法可以用于对象?我有一堆包含“Role”对象的数组,我需要将它们合并,然后去除重复的元素 :)


1
重复的内容:https://dev59.com/l0jSa4cB1Zd3GeqPDC7E - Peter Bailey
此问题缺少其 [mcve]。 - mickmackusa
15个回答

192

array_unique函数可以对一个使用SORT_REGULAR排序的对象数组进行去重:

class MyClass {
    public $prop;
}

$foo = new MyClass();
$foo->prop = 'test1';

$bar = $foo;

$bam = new MyClass();
$bam->prop = 'test2';

$test = array($foo, $bar, $bam);

print_r(array_unique($test, SORT_REGULAR));

将打印:

Array (
    [0] => MyClass Object
        (
            [prop] => test1
        )

    [2] => MyClass Object
        (
            [prop] => test2
        )
)

在这里查看它的运行情况: http://3v4l.org/VvonH#v529

警告:它将使用"=="比较,而不是严格比较("===")。

因此,如果你想从对象数组中删除重复项,请注意它会比较每个对象属性,而不是比较对象身份(实例)。


12
这个答案比被采纳的答案好得多。但是例子没有展示数值比较(==)和身份比较(===)之间的区别,因为$bam->prop = 'test2';(应该改为'test1'来展示差异)。请参考http://codepad.viper-7.com/8NxWhG的示例。 - Flip
@vishal 请查看官方文档:http://php.net/manual/zh/function.array-unique.php - Matthieu Napoli
1
应该是被接受的答案。 而且这比使用__toString()要快得多。请参见:http://sandbox.onlinephpfunctions.com/code/21152125900b0c139dce9a144c9842056b0cd1d8,以获取比较结果。 - LucaM
2
注意不要使用array_unique()函数比较对象,该函数将应用深度比较,如果涉及太多递归可能会导致服务器崩溃——我在看你Doctrine实体。最好确定使您的对象唯一的内容,并使用其索引对象。例如,如果您的对象具有字符串标识符,请使用该标识符作为键构建数组。 - olvlvl
这个能够在使用所有修饰符类型(private, protected, public)的字段上工作吗? - james
array_unique( array_merge($array1, $array2), SORT_REGULAR); 这个神奇的 SORT_REGULAR 完美地实现了这一点。仅凭这一点就应该被标记为正确答案,因为许多答案描述了复杂的过滤或添加函数的方法,其中大多数都不能按照描述的那样工作。对此表示赞扬。 - Ian Tearle

109

好的,array_unique() 函数比较元素的字符串值:

注意:仅当(string)$elem1 === (string)$elem2时,两个元素被视为相等,即当字符串表示相同时,将使用第一个元素。

因此,请确保在您的类中实现__toString() 方法,并且它输出相同的值来代表相等的角色,例如:

class Role {
    private $name;

    //.....

    public function __toString() {
        return $this->name;
    }

}

如果两个角色具有相同的名称,则认为它们是相等的。


2
@Jacob,因为array_unique__toString()都没有进行任何比较。__toString()定义了对象实例在字符串上下文中的行为方式,而array_unique返回已删除重复值的输入数组。它只是在内部使用比较。 - Gordon
1
@Jacob Relkin:这不是比较器,而是对象的字符串表示形式。我认为他们使用它是因为你可以将任何类型、对象等转换为字符串。但是对象上的字符串方法并不仅仅被这个函数使用。例如,echo $object也使用了__toString方法。 - Felix Kling
4
__toString()方法添加到您的所有对象中比仅将SORT_REGULAR标志添加到array_unique要麻烦得多,参见Matthieu Napoli的答案。此外,__toString()方法有许多其他用途,而不仅仅是用于对象比较,因此这可能甚至不可行。 - Flip

36

由于 PHP 5 中比较对象的性质,因此此答案使用 in_array()。利用此对象比较行为需要数组包含对象,但在这里似乎是这种情况。

$merged = array_merge($arr, $arr2);
$final  = array();

foreach ($merged as $current) {
    if ( ! in_array($current, $final)) {
        $final[] = $current;
    }
}

var_dump($final);

1
工作得很好,可能比另一个更快(不太清楚),但我会使用你的,因为我不必为此编写额外的函数:D - Gigala
比较对象时,它们必须具有相同数量的字段,并且必须是相同的键/值对才能被认为是相同的,对吗?我的意思是......如果我有2个对象,其中一个对象有一个额外的字段,那么这些对象将不被认为是“相同的”。 - ChuckKelly
2
in_array 应该使用 $strict 参数!否则,您将使用 "==" 而不是 "===" 来比较对象。更多信息请参见:http://fr2.php.net/manual/fr/function.in-array.php - Matthieu Napoli
2
这里故意选择不使用严格参数。我想找到“相等”的对象,而不一定是同一个实例的对象。这在答案中提到的链接中有解释,其中说:“当使用比较运算符(==)时,对象变量以简单的方式进行比较,即:如果它们具有相同的属性和值,并且是同一类的实例,则两个对象实例相等。” - salathe

17

以下是一种从数组中删除重复对象的方法:

<?php
// Here is the array that you want to clean of duplicate elements.
$array = getLotsOfObjects();

// Create a temporary array that will not contain any duplicate elements
$new = array();

// Loop through all elements. serialize() is a string that will contain all properties
// of the object and thus two objects with the same contents will have the same
// serialized string. When a new element is added to the $new array that has the same
// serialized value as the current one, then the old value will be overridden.
foreach($array as $value) {
    $new[serialize($value)] = $value;
}

// Now $array contains all objects just once with their serialized version as string.
// We don't care about the serialized version and just extract the values.
$array = array_values($new);

这对我来说是最好的解决方案!我正在使用此解决方案进行我的网站搜索引擎(合并数据库中的2个查询结果)。首先,我有所有搜索词的结果,然后将它们与某些搜索词的结果合并。通过这种解决方案,我首先获得了最重要的结果,并添加了其他独特的解决方案。 - Finduilas

13

您也可以先进行序列化:

$unique = array_map( 'unserialize', array_unique( array_map( 'serialize', $array ) ) );

从 PHP 5.2.9 开始,您可以直接使用可选的 sort_flag SORT_REGULAR

$unique = array_unique( $array, SORT_REGULAR );

12

如果您想根据特定属性过滤对象,也可以使用they array_filter函数:

//filter duplicate objects
$collection = array_filter($collection, function($obj)
{
    static $idList = array();
    if(in_array($obj->getId(),$idList)) {
        return false;
    }
    $idList []= $obj->getId();
    return true;
});

6

从这里开始:http://php.net/manual/en/function.array-unique.php#75307

这个函数也适用于对象和数组。

<?php
function my_array_unique($array, $keep_key_assoc = false)
{
    $duplicate_keys = array();
    $tmp         = array();       

    foreach ($array as $key=>$val)
    {
        // convert objects to arrays, in_array() does not support objects
        if (is_object($val))
            $val = (array)$val;

        if (!in_array($val, $tmp))
            $tmp[] = $val;
        else
            $duplicate_keys[] = $key;
    }

    foreach ($duplicate_keys as $key)
        unset($array[$key]);

    return $keep_key_assoc ? $array : array_values($array);
}
?>

2

如果您需要从数组中过滤重复的实例(即“ ===”比较),并且:

  • 您确定数组仅包含对象
  • 您不需要保留键

那么最好的方法是:

//sample data
$o1 = new stdClass;
$o2 = new stdClass;
$arr = [$o1,$o1,$o2];

//algorithm
$unique = [];
foreach($arr as $o){
  $unique[spl_object_hash($o)]=$o;
}
$unique = array_values($unique);//optional - use if you want integer keys on output

2

如果您有一个对象的索引数组,并且想要通过比较每个对象中的特定属性来删除重复项,则可以使用下面类似于remove_duplicate_models()的函数。

原始答案:最初的回答

class Car {
    private $model;

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

    public function get_model() {
        return $this->model;
    }
}

$cars = [
    new Car('Mustang'),
    new Car('F-150'),
    new Car('Mustang'),
    new Car('Taurus'),
];

function remove_duplicate_models( $cars ) {
    $models = array_map( function( $car ) {
        return $car->get_model();
    }, $cars );

    $unique_models = array_unique( $models );

    return array_values( array_intersect_key( $cars, $unique_models ) );
}

print_r( remove_duplicate_models( $cars ) );

最终结果为:
Array
(
    [0] => Car Object
        (
            [model:Car:private] => Mustang
        )

    [1] => Car Object
        (
            [model:Car:private] => F-150
        )

    [2] => Car Object
        (
            [model:Car:private] => Taurus
        )

)

1
你还可以使用回调函数将数组去重(例如,如果你想比较对象的属性或其他方法)。以下是我为此目的使用的通用函数:
/**
* Remove duplicate elements from an array by comparison callback.
*
* @param array $array : An array to eliminate duplicates by callback
* @param callable $callback : Callback accepting an array element returning the value to compare.
* @param bool $preserveKeys : Add true if the keys should be perserved (note that if duplicates eliminated the first key is used).
* @return array: An array unique by the given callback
*/
function unique(array $array, callable $callback, bool $preserveKeys = false): array
{
    $unique = array_intersect_key($array, array_unique(array_map($callback, $array)));
    return ($preserveKeys) ? $unique : array_values($unique);
}

使用示例:

$myUniqueArray = unique($arrayToFilter,
    static function (ExamQuestion $examQuestion) {
        return $examQuestion->getId();
    }
);

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