Jak wyszukiwać według klucza => wartości w wielowymiarowej tablicy w PHP

147

Czy istnieje szybki sposób na uzyskanie wszystkich podtablic, w przypadku których znaleziono parę klucz-wartość w tablicy wielowymiarowej? Nie mogę powiedzieć, jak głęboka będzie tablica.

Prosta tablica przykładowa:

$arr = array(0 => array(id=>1,name=>"cat 1"),
             1 => array(id=>2,name=>"cat 2"),
             2 => array(id=>3,name=>"cat 1")
);

Kiedy wyszukuję klucz = nazwa i wartość = „kot 1”, funkcja powinna zwrócić:

array(0 => array(id=>1,name=>"cat 1"),
      1 => array(id=>3,name=>"cat 1")
);

Myślę, że funkcja musi być rekurencyjna, aby zejść do najgłębszego poziomu.


źródło

Odpowiedzi:

217

Kod:

function search($array, $key, $value)
{
    $results = array();

    if (is_array($array)) {
        if (isset($array[$key]) && $array[$key] == $value) {
            $results[] = $array;
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, search($subarray, $key, $value));
        }
    }

    return $results;
}

$arr = array(0 => array(id=>1,name=>"cat 1"),
             1 => array(id=>2,name=>"cat 2"),
             2 => array(id=>3,name=>"cat 1"));

print_r(search($arr, 'name', 'cat 1'));

Wynik:

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => cat 1
        )

    [1] => Array
        (
            [id] => 3
            [name] => cat 1
        )

)

Jeśli wydajność jest ważna, możesz ją zapisać, aby wszystkie wywołania rekurencyjne zapisywały swoje wyniki w tej samej tymczasowej $resultstablicy zamiast scalać tablice razem, na przykład:

function search($array, $key, $value)
{
    $results = array();
    search_r($array, $key, $value, $results);
    return $results;
}

function search_r($array, $key, $value, &$results)
{
    if (!is_array($array)) {
        return;
    }

    if (isset($array[$key]) && $array[$key] == $value) {
        $results[] = $array;
    }

    foreach ($array as $subarray) {
        search_r($subarray, $key, $value, $results);
    }
}

Kluczem jest to, że search_rczwarty parametr przyjmuje jako odniesienie, a nie wartość; znak ampersand &ma kluczowe znaczenie.

FYI: Jeśli masz starszą wersję PHP następnie trzeba określić udział pass-by-reference na wezwanie do search_raniżeli w swojej deklaracji. Oznacza to, że ostatnia linia staje się search_r($subarray, $key, $value, &$results).

John Kugelman
źródło
2
@JohnKugelman Czy nie zostanie wyświetlony „wydajny” błąd odpowiedzi, jeśli $keynie istnieje w tablicy? Czy nie byłoby lepiej if (array_key_exists($key, $array) && $array[$key] == $value) {?
Chase
1
@JohnKugelman Ta funkcja działa dobrze, ale czasami mam moje, $valuektóre jest, nulla funkcja nie działa ... array empty... Jak mieć tablicę, nawet jeśli $value= null? jak search($array, 'id', null)?
Zagloo
71

A może zamiast tego wersja SPL ? Zaoszczędzi ci to pisania:

// I changed your input example to make it harder and
// to show it works at lower depths:

$arr = array(0 => array('id'=>1,'name'=>"cat 1"),
             1 => array(array('id'=>3,'name'=>"cat 1")),
             2 => array('id'=>2,'name'=>"cat 2")
);

//here's the code:

    $arrIt = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr));

 foreach ($arrIt as $sub) {
    $subArray = $arrIt->getSubIterator();
    if ($subArray['name'] === 'cat 1') {
        $outputArray[] = iterator_to_array($subArray);
    }
}

Wspaniałe jest to, że w zasadzie ten sam kod będzie iterował przez katalog za Ciebie, używając RecursiveDirectoryIterator zamiast RecursiveArrayIterator. SPL jest roxorem.

Jedynym minusem SPL jest to, że jest źle udokumentowany w sieci. Jednak kilka książek poświęconych PHP zawiera przydatne szczegóły, szczególnie Pro PHP; i prawdopodobnie możesz też wygooglować więcej informacji.

jared
źródło
Działa to jak urok i planuję użyć go ponownie w przypadku podobnych problemów: D Jedyną dziwną częścią jest foreach i użycie funkcji getSubIterator w RecursiveIteratorIterator zamiast zmiennej $ sub. Na początku myślałem, że to literówka, ale to właściwy sposób! dzięki Jared.
bchhun
2
Niesamowite rozwiązanie. Dość szybko też!
TaylorOtwell
Dziękuję za rozwiązanie. Skąd bierzemy „identyfikator”? Z $ outputArray?
trante
Dzięki, bardzo proste rozwiązanie, ale nie wiem jak działa ??.
Mahesh.D,
jak usunąć znaleziony element (może to być podtablica) z oryginalnej tablicy?
Fr0zenFyr
49
<?php
$arr = array(0 => array("id"=>1,"name"=>"cat 1"),
             1 => array("id"=>2,"name"=>"cat 2"),
             2 => array("id"=>3,"name"=>"cat 1")
);
$arr = array_filter($arr, function($ar) {
   return ($ar['name'] == 'cat 1');
   //return ($ar['name'] == 'cat 1' AND $ar['id'] == '3');// you can add multiple conditions
});

echo "<pre>";
print_r($arr);

?>

Ref: http://php.net/manual/en/function.array-filter.php

Prasanth Bendra
źródło
4
Jest to dobre rozwiązanie, jeśli chcesz przeszukać tablicę, która ma tylko jeden poziom głębokości, ale to konkretne pytanie dotyczyło wyszukiwania rekurencyjnego w głębokiej tablicy („funkcja musi być rekurencyjna, aby zejść na najgłębszy poziom”).
orrd
16

Wróciłem, aby opublikować tę aktualizację dla każdego, kto potrzebuje porady dotyczącej optymalizacji tych odpowiedzi, w szczególności świetnej odpowiedzi Johna Kugelmana powyżej.

Jego opublikowana funkcja działa dobrze, ale musiałem zoptymalizować ten scenariusz do obsługi zestawu wyników 12 000 wierszy. Funkcja ta potrzebowała wiecznych 8 sekund na przejrzenie wszystkich rekordów, ale za długo.

Po prostu potrzebowałem funkcji STOP wyszukiwania i powrotu po znalezieniu dopasowania. To znaczy, jeśli szukasz customer_id, wiemy, że mamy tylko jeden w zbiorze wyników i po znalezieniu customer_id w tablicy wielowymiarowej, chcemy zwrócić.

Oto zoptymalizowana pod kątem szybkości (i znacznie uproszczona) wersja tej funkcji dla każdego, kto jej potrzebuje. W przeciwieństwie do innych wersji, obsługuje tylko jedną głębokość tablicy, nie powtarza się i eliminuje scalanie wielu wyników.

// search array for specific key = value
public function searchSubArray(Array $array, $key, $value) {   
    foreach ($array as $subarray){  
        if (isset($subarray[$key]) && $subarray[$key] == $value)
          return $subarray;       
    } 
}

Spowodowało to skrócenie czasu zadania dopasowania 12 000 rekordów do 1,5 sekundy. Wciąż bardzo kosztowne, ale znacznie bardziej rozsądne.

stefgosselin
źródło
ten jest szybszy niż Jhon / odpowiedź Jareda (+0,0009999275207519) vs (+0,0020008087158203) .. Więc ten test jest specyficzny do mojego przypadku i środowiska .. Im trzyma się tego, dzięki stefgosselin
Awena
14
if (isset($array[$key]) && $array[$key] == $value)

Drobne poprawki do szybkiej wersji.

blackmogu
źródło
2
W rzeczywistości zapobiega to wysyłaniu ostrzeżeń, gdy klucz nie jest ustawiony. Nie takie drobne! -> +1.
stefgosselin
2
zgodził się, możliwość przejrzenia dziennika błędów php pod kątem poważnych błędów i nieskażania go ostrzeżeniami jest moim zdaniem właściwą drogą.
Codercake
Nie jest to kompletne rozwiązanie, a więc raczej „próba odpowiedzi na inny post” i „brak odpowiedzi”.
mickmackusa
7

Uważaj na algorytmy wyszukiwania liniowego (powyższe są liniowe) w tablicach wielowymiarowych, ponieważ mają złożoną złożoność, ponieważ jej głębokość zwiększa liczbę iteracji wymaganych do przejścia całej tablicy. Na przykład:

array(
    [0] => array ([0] => something, [1] => something_else))
    ...
    [100] => array ([0] => something100, [1] => something_else100))
)

zajęłoby co najwyżej 200 iteracji, aby znaleźć to, czego szukasz (gdyby igła miała wartość [100] [1]), przy użyciu odpowiedniego algorytmu.

Algorytmy liniowe w tym przypadku działają w O (n) (uporządkuj całkowitą liczbę elementów w całej tablicy), jest to słabe, milion wpisów (np. Tablica 1000x100x10) wymagałoby średnio 500 000 iteracji, aby znaleźć igłę. Co by się stało, gdybyś zdecydował się zmienić strukturę swojej wielowymiarowej tablicy? A PHP wykopałby algorytm rekurencyjny, gdyby twoja głębokość była większa niż 100. Informatyka może zrobić lepiej:

Tam, gdzie to możliwe, zawsze używaj obiektów zamiast tablic wielowymiarowych:

ArrayObject(
   MyObject(something, something_else))
   ...
   MyObject(something100, something_else100))
)

i zastosuj niestandardowy interfejs i funkcję komparatora, aby je posortować i znaleźć:

interface Comparable {
   public function compareTo(Comparable $o);
}

class MyObject implements Comparable {
   public function compareTo(Comparable $o){
      ...
   }
}

function myComp(Comparable $a, Comparable $b){
    return $a->compareTo($b);
}

Możesz użyć uasort()niestandardowego komparatora, jeśli masz ochotę na przygodę, powinieneś zaimplementować własne kolekcje dla swoich obiektów, które mogą je sortować i zarządzać nimi (zawsze rozszerzam ArrayObject, aby zawierał przynajmniej funkcję wyszukiwania).

$arrayObj->uasort("myComp");

Po posortowaniu (uasort to O (n log n), co jest tak dobre, jak w przypadku dowolnych danych), wyszukiwanie binarne może wykonać operację w czasie O (log n), tj. Milion wpisów zajmuje tylko ~ 20 iteracji Szukaj. O ile wiem, wyszukiwanie binarne niestandardowego komparatora nie jest zaimplementowane w PHP (array_search() używa naturalnego porządku, który działa na odwołaniach do obiektów, a nie na ich właściwościach), musiałbyś to zaimplementować samodzielnie, tak jak ja.

To podejście jest bardziej wydajne (nie ma już głębi) i, co ważniejsze, uniwersalne (zakładając, że wymuszasz porównywalność za pomocą interfejsów), ponieważ obiekty definiują sposób ich sortowania, dzięki czemu można nieskończenie powtarzać kod. Znacznie lepiej =)

mbdxgdb2
źródło
Ta odpowiedź powinna być poprawna. Chociaż metoda wyszukiwania brutalnej siły to zrobi, wymaga to znacznie mniej zasobów.
Drew
Należy zauważyć, że to, co sugerujesz, ma sens tylko wtedy, gdy przeszukujesz tę samą tablicę wiele razy. Dużo więcej czasu zajmuje wykonanie problemu z sortowaniem (O (n log n)), niż po prostu wykonanie liniowego wyszukiwania wartości (O (n)). Ale po posortowaniu, jasne, wyszukiwanie binarne byłoby szybsze.
orrd
Powinienem również dodać, że używanie obiektów zamiast tablic może być użyteczną abstrakcją, ale możesz również przeprowadzić wyszukiwanie binarne w tablicy, jeśli tablica jest posortowana. Nie musisz używać obiektów do sortowania tablicy ani do przeszukiwania jej binarnie.
orrd
6

Oto rozwiązanie:

<?php
$students['e1003']['birthplace'] = ("Mandaluyong <br>");
$students['ter1003']['birthplace'] = ("San Juan <br>");
$students['fgg1003']['birthplace'] = ("Quezon City <br>");
$students['bdf1003']['birthplace'] = ("Manila <br>");

$key = array_search('Delata Jona', array_column($students, 'name'));
echo $key;  

?>
Tristan
źródło
5
$result = array_filter($arr, function ($var) {   
  $found = false;
  array_walk_recursive($var, function ($item, $key) use (&$found) {  
    $found = $found || $key == "name" && $item == "cat 1";
  });
  return $found;
});
Vitalii Fedorenko
źródło
3

http://snipplr.com/view/51108/nested-array-search-by-value-or-key/

<?php

//PHP 5.3

function searchNestedArray(array $array, $search, $mode = 'value') {

    foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $value) {
        if ($search === ${${"mode"}})
            return true;
    }
    return false;
}

$data = array(
    array('abc', 'ddd'),
    'ccc',
    'bbb',
    array('aaa', array('yyy', 'mp' => 555))
);

var_dump(searchNestedArray($data, 555));
Pramendra Gupta
źródło
3
function in_multi_array($needle, $key, $haystack) 
{
    $in_multi_array = false;
    if (in_array($needle, $haystack))
    {
        $in_multi_array = true; 
    }else 
    {
       foreach( $haystack as $key1 => $val )
       {
           if(is_array($val)) 
           {
               if($this->in_multi_array($needle, $key, $val)) 
               {
                   $in_multi_array = true;
                   break;
               }
           }
        }
    }

    return $in_multi_array;
} 
radhe
źródło
mój przypadek jest inny, ale otrzymałem wskazówkę z twojej odpowiedzi.
shyammakwana.me
2

Potrzebowałem czegoś podobnego, ale aby wyszukać tablicę wielowymiarową według wartości ... Wziąłem przykład Johna i napisałem

function _search_array_by_value($array, $value) {
        $results = array();
        if (is_array($array)) {
            $found = array_search($value,$array);
            if ($found) {
                $results[] = $found;
            }
            foreach ($array as $subarray)
                $results = array_merge($results, $this->_search_array_by_value($subarray, $value));
        }
        return $results;
    }

Mam nadzieję, że komuś to pomoże :)

confiq
źródło
2

To jest poprawiona funkcja z tej, którą opublikował John K. ... Muszę pobrać tylko określony klucz z tablicy i nic ponad nim.

function search_array ( $array, $key, $value )
{
    $results = array();

    if ( is_array($array) )
    {
        if ( $array[$key] == $value )
        {
            $results[] = $array;
        } else {
            foreach ($array as $subarray) 
                $results = array_merge( $results, $this->search_array($subarray, $key, $value) );
        }
    }

    return $results;
}

$arr = array(0 => array(id=>1,name=>"cat 1"),
       1 => array(id=>2,name=>"cat 2"),
       2 => array(id=>3,name=>"cat 1"));

print_r(search_array($arr, 'name', 'cat 1'));
Trevor Lettman
źródło
1

I inna wersja, która zwraca wartość klucza z elementu tablicy, w którym wartość została znaleziona (bez rekursji, zoptymalizowana pod kątem szybkości):

// if the array is 
$arr['apples'] = array('id' => 1);
$arr['oranges'] = array('id' => 2);

//then 
print_r(search_array($arr, 'id', 2);
// returns Array ( [oranges] => Array ( [id] => 2 ) ) 
// instead of Array ( [0] => Array ( [id] => 2 ) )

// search array for specific key = value
function search_array($array, $key, $value) {
  $return = array();   
  foreach ($array as $k=>$subarray){  
    if (isset($subarray[$key]) && $subarray[$key] == $value) {
      $return[$k] = $subarray;
      return $return;
    } 
  }
}

Dziękuję wszystkim, którzy tutaj napisali.

Darko Hrgovic
źródło
1
function findKey($tab, $key){
    foreach($tab as $k => $value){ 
        if($k==$key) return $value; 
        if(is_array($value)){ 
            $find = findKey($value, $key);
            if($find) return $find;
        }
    }
    return null;
}
Monaem AMINA
źródło
2
Czy mógłbyś rozwinąć tę odpowiedź? Odpowiedzi zawierające tylko kod nie wyjaśniają, co faktycznie robisz.
Rich Benner
Zaktualizuj swoje pytanie, aby uczyć.
mickmackusa
To działa tylko dla funkcji Znajdź klucz, to działa dla mnie.
Giovanny Gonzalez
0

Jeśli chcesz wyszukać tablicę kluczy, to dobrze

function searchKeysInMultiDimensionalArray($array, $keys)
{
    $results = array();

    if (is_array($array)) {
        $resultArray = array_intersect_key($array, array_flip($keys));
        if (!empty($resultArray)) {
            $results[] = $resultArray;
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
        }
    }

    return $results;
}

Klucze nie zostaną nadpisane, ponieważ każdy zestaw wartości klucz => będzie w oddzielnej tablicy w wynikowej tablicy.
Jeśli nie chcesz zduplikowanych kluczy, użyj tego

function searchKeysInMultiDimensionalArray($array, $keys)
{
    $results = array();

    if (is_array($array)) {
        $resultArray = array_intersect_key($array, array_flip($keys));
        if (!empty($resultArray)) {
            foreach($resultArray as $key => $single) {

                $results[$key] = $single;
            }
        }

        foreach ($array as $subarray) {
            $results = array_merge($results, searchKeysInMultiDimensionalArray($subarray, $keys));
        }
    }

    return $results;
}
Pankaj
źródło