array_unique für Objekte?

82

Gibt es eine Methode wie array_unique für Objekte? Ich habe eine Reihe von Arrays mit 'Rollen'-Objekten, die ich zusammenführe, und dann möchte ich die Duplikate entfernen :)

Johannes
quelle
1
Duplikat von stackoverflow.com/questions/1050709/…
Peter Bailey

Antworten:

95

Nun, array_unique()vergleicht den String - Wert der Elemente:

Hinweis : Zwei Elemente werden genau dann als gleich angesehen, wenn bei gleicher (string) $elem1 === (string) $elem2Zeichenfolgendarstellung das erste Element verwendet wird.

Stellen Sie daher sicher, dass die __toString()Methode in Ihrer Klasse implementiert ist und dass sie denselben Wert für gleiche Rollen ausgibt, z

class Role {
    private $name;

    //.....

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

}

Dies würde zwei Rollen als gleich betrachten, wenn sie denselben Namen haben.

Felix Kling
quelle
2
@ Jacob, weil weder array_uniquenoch __toString()etwas vergleichen. __toString()Definiert, wie sich eine Objektinstanz bei Verwendung in einem Zeichenfolgenkontext verhalten soll, und array_uniquegibt das Eingabearray mit entfernten doppelten Werten zurück. Es wird nur intern ein Vergleich verwendet .
Gordon
1
@ Jacob Relkin: Es ist kein Komparator. Es ist die Zeichenfolgendarstellung des Objekts. Ich denke, sie verwenden dies, da Sie jeden Typ, jedes Objekt usw. in eine Zeichenfolge konvertieren können. Die String-Methode selbst für ein Objekt wird jedoch nicht nur von dieser Funktion verwendet. ZB echo $objectverwendet auch die __toStringMethode.
Felix Kling
3
Das Hinzufügen von __toString()Methoden zu all Ihren Objekten ist viel schmerzhafter als das Hinzufügen eines SORT_REGULARFlags zu array_unique, siehe Matthieu Napoli seine Antwort. Außerdem hat eine __toString()Methode viele andere Anwendungsfälle, die dann für den Objektvergleich verwendet werden, so dass dies möglicherweise nicht einmal möglich ist.
Flip
154

array_uniquearbeitet mit einer Reihe von Objekten mit 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));

Wird drucken:

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

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

Sehen Sie es hier in Aktion: http://3v4l.org/VvonH#v529

Warnung : Es wird der Vergleich "==" verwendet, nicht der strikte Vergleich ("===").

Wenn Sie also Duplikate in einem Array von Objekten entfernen möchten, achten Sie darauf, dass die einzelnen Objekteigenschaften verglichen werden und nicht die Objektidentität (Instanz).

Matthieu Napoli
quelle
12
Diese Antwort ist viel besser als die akzeptierte Antwort. Das Beispiel zeigt jedoch nicht den Unterschied zwischen dem Vergleich von Wert ( ==) oder Identität ( ===) aufgrund von $bam->prop = 'test2';(sollte 'test1'den Unterschied darstellen). Ein Beispiel finden Sie unter codepad.viper-7.com/8NxWhG .
Flip
@vishal werfen Sie einen Blick auf die offizielle Dokumentation: php.net/manual/en/function.array-unique.php
Matthieu Napoli
1
Du hast mich gerettet, Bruder, TnQ sehr: *
Saman Sattari
1
Sollte die akzeptierte Antwort sein. Auch dies viel schneller als mit __toString (). Das Vergleichsergebnis finden Sie unter: sandbox.onlinephpfunctions.com/code/… .
LucaM
1
Achten Sie darauf, Objekte mit array_unique () zu vergleichen. Die Funktion wendet einen umfassenden Vergleich an, der zum Absturz Ihres Servers führen kann, wenn zu viel Rekursion erforderlich ist. Ich sehe Sie als Doctrine-Entitäten. Identifizieren Sie besser, was Ihr Objekt einzigartig macht, und indizieren Sie Ihre Objekte damit. Wenn Ihre Objekte beispielsweise eine Zeichenfolgenkennung haben, erstellen Sie ein Array mit dieser Kennung als Schlüssel.
Olvlvl
31

Diese Antwort wird verwendet, in_array()da die Art des Vergleichens von Objekten in PHP 5 dies ermöglicht. Um dieses Objektvergleichsverhalten nutzen zu können, muss das Array nur Objekte enthalten. Dies scheint hier jedoch der Fall zu sein.

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

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

var_dump($final);
salathe
quelle
1
Funktioniert gut, es ist vielleicht schneller als das andere (weiß es nicht wirklich), aber ich werde deins verwenden, weil ich keine zusätzliche Funktion dafür übernehmen muss: D
Gigala
Beim Vergleichen von Objekten müssen sie die gleiche Anzahl von Feldern und identische Schlüssel / Wert-Paare haben, um als gleich richtig angesehen zu werden. Was ich
vorhabe
1
in_arraysollte den $strictParameter verwenden! Andernfalls vergleichen Sie Objekte mit "==" anstelle von "===". Mehr hier: fr2.php.net/manual/fr/function.in-array.php
Matthieu Napoli
1
Die Nichtverwendung des strengen Parameters war hier eine bewusste Wahl. Ich wollte "gleiche" Objekte finden, nicht unbedingt die gleiche Instanz eines Objekts. Dies wird in dem in der Antwort erwähnten Link erklärt, der besagt: " Bei Verwendung des Vergleichsoperators (==) werden Objektvariablen auf einfache Weise verglichen, nämlich: Zwei Objektinstanzen sind gleich, wenn sie dieselben Attribute und Werte haben. und sind Instanzen derselben Klasse. "
salathe
Klappt wunderbar! Vielen Dank
Gronaz
16

So entfernen Sie doppelte Objekte in einem Array:

<?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);
Yankee
quelle
Das ist für mich die beste Lösung! Ich verwende diese Lösung für meine Website searchengine (füge 2 Abfrageergebnisse aus einer Datenbank zusammen). Zuerst habe ich die Ergebnisse für alle Suchbegriffe und füge sie mit den Ergebnissen einiger Suchbegriffe zusammen. Mit dieser Lösung habe ich die wichtigsten Ergebnisse zuerst, hinzugefügt durch einzigartige andere Lösungen.
Finduilas
11

Sie können auch zuerst serialisieren:

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

Ab PHP 5.2.9 können Sie nur optional verwenden sort_flag SORT_REGULAR:

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

Sie können auch die Funktion array_filter verwenden, wenn Sie Objekte anhand eines bestimmten Attributs filtern möchten:

//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
quelle
6

Von hier: http://php.net/manual/en/function.array-unique.php#75307

Dieser würde auch mit Objekten und Arrays arbeiten.

<?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);
}
?>
Silbernes Licht
quelle
2

Wenn Sie über ein indiziertes Array von Objekten verfügen und Duplikate durch Vergleichen einer bestimmten Eigenschaft in jedem Objekt entfernen möchten, kann eine Funktion wie die remove_duplicate_models()folgende verwendet werden.

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

Das Ergebnis ist:

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
quelle
0

Dies ist eine sehr einfache Lösung:

$ids = array();

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

array_unique wandelt die Elemente in einen String um und führt einen Vergleich durch. Wenn Ihre Objekte nicht eindeutig in Zeichenfolgen umgewandelt werden, funktionieren sie nicht mit array_unique.

Implementieren Sie stattdessen eine statusbehaftete Vergleichsfunktion für Ihre Objekte und verwenden Sie array_filter , um Dinge wegzuwerfen, die die Funktion bereits gesehen hat.

jricher
quelle
Ich suchte nach einer eleganteren Lösung (für die keine Rückrufe erforderlich wären). Trotzdem schätzen Sie Ihre Antwort.
Emanuil Rusev
1
array_uniqueverwendet mit SORT_REGULAR funktioniert, siehe meine Antwort unten.
Matthieu Napoli
-1

vernünftiger und schneller Weg, wenn Sie doppelte Instanzen (dh "===" Vergleich) aus dem Array herausfiltern müssen und:

  • Sie sind sicher, welches Array nur Objekte enthält
  • Sie brauchen keine Schlüssel erhalten

ist:

//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
quelle
-1

Auf diese Weise vergleiche ich Objekte mit einfachen Eigenschaften und erhalte gleichzeitig eine eindeutige Sammlung:

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

Wird ausgegeben:

array (size=2)
  'foo' => 
    object(Role)[1165]
      private 'name' => string 'foo' (length=3)
  'bar' => 
    object(Role)[1166]
      private 'name' => string 'bar' (length=3)
Gänserich
quelle
-1

Wenn Sie ein Array von Objekten haben und diese Sammlung filtern möchten, um alle Duplikate zu entfernen, können Sie array_filter mit anonymer Funktion verwenden:

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

Wichtig: Denken Sie daran, dass Sie das $tmpArray als Referenz für Ihre Filter-Rückruffunktion übergeben müssen, da es sonst nicht funktioniert

Krzysztof Raciniewski
quelle