tablica_unique dla obiektów?

Odpowiedzi:

98

Cóż, array_unique()porównuje wartości ciągów elementów:

Uwaga : Dwa elementy są uważane za równe wtedy i tylko wtedy, (string) $elem1 === (string) $elem2gdy np. Gdy reprezentacja łańcuchowa jest taka sama, zostanie użyty pierwszy element.

Dlatego upewnij się, że zaimplementowałeś __toString()metodę w swojej klasie i że daje ona taką samą wartość dla równych ról, np

class Role {
    private $name;

    //.....

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

}

To uznałoby dwie role za równe, gdyby miały taką samą nazwę.

Felix Kling
źródło
2
@Jacob, ponieważ ani array_uniquenie __toString()porównuj niczego. __toString()definiuje, jak instancja obiektu powinna zachowywać się, gdy jest używana w kontekście ciągów i array_uniquezwraca tablicę wejściową z usuniętymi zduplikowanymi wartościami. Po prostu używa porównania do tego wewnętrznie.
Gordon
1
@Jacob Relkin: To nie jest komparator. Jest to ciąg znaków reprezentujący obiekt. Myślę, że używają tego, ponieważ można przekonwertować dowolny typ, obiekt itp. Na ciąg. Ale sama metoda napisów na obiekcie jest używana nie tylko przez tę funkcję. Np. echo $objectRównież używa tej __toStringmetody.
Felix Kling
3
Dodawanie __toString()metod do wszystkich obiektów jest znacznie bardziej bolesne niż po prostu dodawanie SORT_REGULARflagi do array_unique, zobacz odpowiedź Matthieu Napoli. Poza tym __toString()metoda ma wiele innych przypadków użycia używanych do porównywania obiektów, więc może to nawet nie być możliwe.
Flip
156

array_uniquedziała z tablicą obiektów używając 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));

Wydrukuje:

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

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

Zobacz to w akcji tutaj: http://3v4l.org/VvonH#v529

Ostrzeżenie : użyje porównania „==”, a nie ścisłego porównania („===”).

Więc jeśli chcesz usunąć duplikaty wewnątrz tablicy obiektów, uważaj, że porówna on właściwości każdego obiektu, a nie jego tożsamość (instancję).

Matthieu Napoli
źródło
12
Ta odpowiedź jest znacznie lepsza niż zaakceptowana odpowiedź. Jednak przykład nie pokazuje różnicy między porównaniem wartości ( ==) lub tożsamością ( ===) z powodu $bam->prop = 'test2';(powinno być 'test1'pokazanie różnicy). Przykład można znaleźć pod adresem codepad.viper-7.com/8NxWhG .
Flip
@vishal spójrz na oficjalną dokumentację: php.net/manual/en/function.array-unique.php
Matthieu Napoli
1
uratowałeś mnie bracie, TnQ bardzo: *
Saman Sattari
1
Powinna być zaakceptowana odpowiedź. Również to dużo szybciej niż przy użyciu __toString (). Zobacz: sandbox.onlinephpfunctions.com/code/ ... aby zobaczyć wynik porównania.
LucaM,
1
Uważaj, porównując obiekty za pomocą array_unique (), funkcja zastosuje głębokie porównanie, które może spowodować awarię serwera, jeśli wymaga zbyt dużej rekurencji - patrzę na ciebie, encje Doctrine. Lepiej zidentyfikuj, co sprawia, że ​​Twój obiekt jest unikalny i zindeksuj nim swoje obiekty. Na przykład, jeśli obiekty mają identyfikator w postaci łańcucha, utwórz tablicę z tym identyfikatorem jako klucz.
olvlvl
31

Ta odpowiedź jest używana, in_array()ponieważ natura porównywania obiektów w PHP 5 pozwala nam to zrobić. Korzystanie z tego zachowania polegającego na porównywaniu obiektów wymaga, aby tablica zawierała tylko obiekty, ale wydaje się, że tak jest w tym przypadku.

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

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

var_dump($final);
salathe
źródło
1
Działa dobrze, może być szybszy niż ten drugi (nie wiem), ale
użyję
Porównując obiekty, muszą one mieć taką samą liczbę pól i muszą być identycznymi parami klucz / wartość, aby można je było uznać za takie same, prawda? to, do czego zmierzam, to .... jeśli mam 2 obiekty i jeden z nich ma jedno dodatkowe pole, czy te obiekty nie będą uważane za „takie same”
ChuckKelly
1
in_arraypowinien użyć $strictparametru! W przeciwnym razie porównujesz obiekty za pomocą „==” zamiast „===”. Więcej tutaj: fr2.php.net/manual/fr/function.in-array.php
Matthieu Napoli
1
Nieużywanie ścisłego parametru było tutaj celowym wyborem. Chciałem znaleźć „równe” obiekty, niekoniecznie tę samą instancję obiektu. Jest to wyjaśnione w odsyłaczu wymienionym w odpowiedzi, który mówi: „ Używając operatora porównania (==), zmienne obiektów są porównywane w prosty sposób, a mianowicie: Dwie instancje obiektów są równe, jeśli mają te same atrybuty i wartości, i są przykładami tej samej klasy.
salathe
Działa jak marzenie!
Wielkie
17

Oto sposób na usunięcie zduplikowanych obiektów z tablicy:

<?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);
Jankes
źródło
To dla mnie najlepsze rozwiązanie! Używam tego rozwiązania do wyszukiwania mojej witryny internetowej (scalanie 2 wyników zapytań z bazy danych). Najpierw mam wyniki wszystkich terminów wyszukiwania i łączę je z wynikami niektórych terminów wyszukiwania. Dzięki temu rozwiązaniu mam najważniejsze wyniki jako pierwsze, dodane przez inne unikalne rozwiązania ..
Finduilas
11

Możesz również serializować najpierw:

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

Od PHP 5.2.9 możesz po prostu użyć opcjonalnego sort_flag SORT_REGULAR:

$unique = array_unique( $array, SORT_REGULAR );
Remon
źródło
9

Możesz również użyć ich funkcji array_filter, jeśli chcesz filtrować obiekty na podstawie określonego atrybutu:

//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
źródło
6

Stąd: http://php.net/manual/en/function.array-unique.php#75307

Ten działałby również z obiektami i tablicami.

<?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);
}
?>
Srebrne światło
źródło
2

Jeśli masz indeksowaną tablicę obiektów i chcesz usunąć duplikaty, porównując określoną właściwość w każdym obiekcie, remove_duplicate_models()możesz użyć funkcji podobnej do poniższej.

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

Wynik to:

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
źródło
0

rozsądny i szybki sposób, jeśli chcesz odfiltrować zduplikowane instancje (np. porównanie „===”) z tablicy i:

  • jesteś pewien, która tablica zawiera tylko obiekty
  • nie potrzebujesz zachowanych kluczy

jest:

//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 Bułhakow
źródło
0

To bardzo proste rozwiązanie:

$ids = array();

foreach ($relate->posts as $key => $value) {
  if (!empty($ids[$value->ID])) { unset($relate->posts[$key]); }
  else{ $ids[$value->ID] = 1; }
}
Artem Sivak
źródło
-1

array_unique działa poprzez rzutowanie elementów na łańcuch i wykonanie porównania. O ile twoje obiekty nie są jednoznacznie rzutowane na łańcuchy, to nie będą działać z array_unique.

Zamiast tego zaimplementuj stanową funkcję porównującą dla swoich obiektów i użyj array_filter, aby wyrzucić rzeczy, które funkcja już widziała.

jricher
źródło
Liczyłem na bardziej eleganckie rozwiązanie (takie, które nie wymagałoby callbacków). Niemniej jednak doceniam twoją odpowiedź.
Emanuil Rusev
1
array_uniqueużywany z pracami SORT_REGULAR, zobacz moją odpowiedź poniżej.
Matthieu Napoli
-1

To mój sposób na porównywanie obiektów o prostych właściwościach, a jednocześnie na otrzymanie unikalnej kolekcji:

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

Wyświetli:

array (size=2)
  'foo' => 
    object(Role)[1165]
      private 'name' => string 'foo' (length=3)
  'bar' => 
    object(Role)[1166]
      private 'name' => string 'bar' (length=3)
Gąsior
źródło
-1

Jeśli masz tablicę obiektów i chcesz przefiltrować tę kolekcję, aby usunąć wszystkie duplikaty, możesz użyć array_filter z funkcją anonimową:

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

Ważne: Pamiętaj, że musisz przekazać $tmptablicę jako odniesienie do funkcji wywołania zwrotnego filtru, w przeciwnym razie nie zadziała

Krzysztof Raciniewski
źródło