array_unique para objetos?

Respuestas:

97

Bueno, array_unique()compara el valor de cadena de los elementos:

Nota : Dos elementos se consideran iguales si y solo si, (string) $elem1 === (string) $elem2es decir, cuando la representación de la cadena es la misma, se utilizará el primer elemento.

Así que asegúrese de implementar el __toString()método en su clase y que genere el mismo valor para roles iguales, por ejemplo

class Role {
    private $name;

    //.....

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

}

Esto consideraría dos roles como iguales si tuvieran el mismo nombre.

Felix Kling
fuente
2
@Jacob porque ni array_uniqueni __toString()comparo nada. __toString()define cómo se supone que se comporta una instancia de objeto cuando se usa en un contexto de cadena y array_uniquedevuelve la matriz de entrada con los valores duplicados eliminados. Solo usa la comparación para esto internamente.
Gordon
1
@Jacob Relkin: No es un comparador. Es la representación de cadena del objeto. Creo que usan esto ya que puedes convertir cualquier tipo, objeto, etc. en una cadena. Pero el método de cadena en sí en un objeto no solo lo usa esta función. Por ejemplo, echo $objecttambién utiliza el __toStringmétodo.
Felix Kling
3
Agregar __toString()métodos a todos sus objetos es mucho más doloroso que simplemente agregar una SORT_REGULARbandera a array_unique, vea Matthieu Napoli su respuesta. Además, un __toString()método tiene muchos otros casos de uso que se utilizan para la comparación de objetos, por lo que es posible que esto ni siquiera sea posible.
Flip
154

array_uniquefunciona con una variedad de objetos usando 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));

Imprimirá:

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

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

Véalo en acción aquí: http://3v4l.org/VvonH#v529

Advertencia : utilizará la comparación "==", no la comparación estricta ("===").

Entonces, si desea eliminar duplicados dentro de una matriz de objetos, tenga en cuenta que comparará las propiedades de cada objeto, no comparará la identidad del objeto (instancia).

Matthieu Nápoles
fuente
12
Esta respuesta es mucho mejor que la respuesta aceptada. Sin embargo, el ejemplo no muestra la diferencia entre la comparación en valor ( ==) o identidad ( ===) debido a $bam->prop = 'test2';(debería ser 'test1'para mostrar la diferencia). Consulte codepad.viper-7.com/8NxWhG para ver un ejemplo.
Flip
@vishal echa un vistazo a la documentación oficial: php.net/manual/en/function.array-unique.php
Matthieu Napoli
1
me salvaste hermano, TnQ mucho: *
Saman Sattari
1
Debería ser la respuesta aceptada. También esto es mucho más rápido que usar __toString (). Consulte: sandbox.onlinephpfunctions.com/code/… para obtener un resultado de comparación.
LucaM
1
Tenga cuidado al comparar objetos con array_unique (), la función aplicará una comparación profunda, que puede provocar que su servidor se bloquee si implica demasiada recursividad; estoy mirando sus entidades Doctrine. Identifique mejor qué hace que su objeto sea único e indexe sus objetos con él. Por ejemplo, si sus objetos tienen un identificador de cadena, cree una matriz con ese identificador como clave.
olvlvl
31

Esta respuesta utiliza in_array()ya que la naturaleza de comparar objetos en PHP 5 nos permite hacerlo. Hacer uso de este comportamiento de comparación de objetos requiere que la matriz solo contenga objetos, pero ese parece ser el caso aquí.

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

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

var_dump($final);
salathe
fuente
1
Funciona bien, puede ser más rápido que el otro (realmente no lo sé) pero
usaré el
Al comparar objetos, deben tener la misma cantidad de campos y deben ser pares clave / valor idénticos para que se consideren iguales, ¿correcto? a lo que me refiero es ... si tengo 2 objetos y uno de ellos tiene un campo adicional, ¿esos objetos no se considerarán "iguales"?
ChuckKelly
1
in_arraydebería usar el $strictparámetro! De lo contrario, puede comparar objetos usando "==" en lugar de "===". Más aquí: fr2.php.net/manual/fr/function.in-array.php
Matthieu Napoli
1
No utilizar el parámetro estricto fue una elección deliberada aquí. Quería encontrar objetos "iguales", no necesariamente la misma instancia de un objeto. Esto se explica en el enlace mencionado en la respuesta, que dice: " Cuando se usa el operador de comparación (==), las variables de objeto se comparan de manera simple, a saber: Dos instancias de objeto son iguales si tienen los mismos atributos y valores, y son instancias de la misma clase "
Salathe
¡Funciona de maravilla! Muchas gracias
gronaz
16

Aquí hay una forma de eliminar objetos duplicados en una matriz:

<?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);
yanqui
fuente
¡Esta es para mí la mejor solución! Estoy usando esta solución para el motor de búsqueda de mi sitio web (fusionar 2 resultados de consulta de una base de datos). Primero tengo los resultados de todos los términos de búsqueda y los combino con los resultados de algunos de los términos de búsqueda. Con esta solución tengo los resultados más importantes primero, agregados por otras soluciones únicas ..
Finduilas
11

También puede serializar primero:

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

A partir de PHP 5.2.9, puede usar opcional sort_flag SORT_REGULAR:

$unique = array_unique( $array, SORT_REGULAR );
Remon
fuente
9

También puede usar la función array_filter, si desea filtrar objetos en función de un atributo específico:

//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;
});
Arvid Vermote
fuente
6

Desde aquí: http://php.net/manual/en/function.array-unique.php#75307

Este también funcionaría con objetos y matrices.

<?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);
}
?>
Luz plateada
fuente
2

Si tiene una matriz indexada de objetos y desea eliminar los duplicados comparando una propiedad específica en cada objeto, remove_duplicate_models()se puede usar una función como la que se muestra a continuación.

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

El resultado es:

Array
(
    [0] => Car Object
        (
            [model:Car:private] => Mustang
        )

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

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

)
Kellen Mace
fuente
0

de forma sana y rápida si necesita filtrar instancias duplicadas (es decir, comparación "===") fuera de la matriz y:

  • estás seguro de qué matriz contiene solo objetos
  • no necesitas llaves conservadas

es:

//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
Roman Bulgakov
fuente
0

Esta es una solución muy simple:

$ids = array();

foreach ($relate->posts as $key => $value) {
  if (!empty($ids[$value->ID])) { unset($relate->posts[$key]); }
  else{ $ids[$value->ID] = 1; }
}
Artem Sivak
fuente
-1

array_unique funciona convirtiendo los elementos en una cadena y haciendo una comparación. A menos que sus objetos se conviertan únicamente en cadenas, no funcionarán con array_unique.

En su lugar, implemente una función de comparación con estado para sus objetos y use array_filter para descartar cosas que la función ya ha visto.

jricher
fuente
Esperaba una solución más elegante (una que no requiriera devoluciones de llamada). Sin embargo, agradezco su respuesta.
Emanuil Rusev
1
array_uniqueusado con trabajos SORT_REGULAR, vea mi respuesta a continuación.
Matthieu Napoli
-1

Esta es mi forma de comparar objetos con propiedades simples y al mismo tiempo recibir una colección única:

class Role {
    private $name;

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

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

$roles = [
    new Role('foo'),
    new Role('bar'),
    new Role('foo'),
    new Role('bar'),
    new Role('foo'),
    new Role('bar'),
];

$roles = array_map(function (Role $role) {
    return ['key' => $role->getName(), 'val' => $role];
}, $roles);

$roles = array_column($roles, 'val', 'key');

var_dump($roles);

Saldrá:

array (size=2)
  'foo' => 
    object(Role)[1165]
      private 'name' => string 'foo' (length=3)
  'bar' => 
    object(Role)[1166]
      private 'name' => string 'bar' (length=3)
Vistazo
fuente
-1

Si tiene una matriz de objetos y desea filtrar esta colección para eliminar todos los duplicados, puede usar array_filter con la función anónima:

$myArrayOfObjects = $myCustomService->getArrayOfObjects();

// This is temporary array
$tmp = [];
$arrayWithoutDuplicates = array_filter($myArrayOfObjects, function ($object) use (&$tmp) {
    if (!in_array($object->getUniqueValue(), $tmp)) {
        $tmp[] = $object->getUniqueValue();
        return true;
    }
    return false;
});

Importante: recuerde que debe pasar la $tmpmatriz como referencia a su función de devolución de llamada de filtro, de lo contrario no funcionará

Krzysztof Raciniewski
fuente