Comment rechercher par clé => valeur dans un tableau multidimensionnel en PHP

147

Existe-t-il un moyen rapide d'obtenir tous les sous-tableaux où une paire clé / valeur a été trouvée dans un tableau multidimensionnel? Je ne peux pas dire quelle sera la profondeur du tableau.

Exemple de tableau simple:

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

Lorsque je recherche key = name et value = "cat 1", la fonction doit renvoyer:

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

Je suppose que la fonction doit être récursive pour aller au niveau le plus profond.


la source

Réponses:

217

Code:

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

Production:

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

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

)

Si l'efficacité est importante, vous pouvez l'écrire pour que tous les appels récursifs stockent leurs résultats dans le même $resultstableau temporaire plutôt que de fusionner des tableaux ensemble, comme ceci:

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

La clé là-bas search_rprend son quatrième paramètre par référence plutôt que par valeur; l'esperluette &est cruciale.

FYI: Si vous avez une ancienne version de PHP, vous devez spécifier la partie pass-by-reference dans l' appel à search_rplutôt que dans sa déclaration. Autrement dit, la dernière ligne devient search_r($subarray, $key, $value, &$results).

John Kugelman
la source
2
@JohnKugelman L'erreur de réponse «efficace» $keyne sortira- t-elle pas si elle n'existe pas dans le tableau? Ne serait-il pas préférable de le faire if (array_key_exists($key, $array) && $array[$key] == $value) {?
Chase le
1
@JohnKugelman Cette fonction fonctionne bien mais parfois j'ai mon $valuequi est nullet la fonction ne fonctionne pas ... array empty... Comment avoir un tableau même si $value= null? comme search($array, 'id', null)?
Zagloo
71

Que diriez - vous de la SPL la version à la place? Cela vous évitera de taper:

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

Ce qui est génial, c'est que fondamentalement, le même code itérera dans un répertoire pour vous, en utilisant un RecursiveDirectoryIterator au lieu d'un RecursiveArrayIterator. SPL est le roxor.

Le seul inconvénient de SPL est qu'il est mal documenté sur le Web. Mais plusieurs livres PHP donnent des détails utiles, en particulier Pro PHP; et vous pouvez probablement rechercher plus d'informations sur Google.

jared
la source
Cela fonctionne comme un charme et je prévois de l'utiliser à nouveau pour des problèmes similaires: D La seule partie étrange est dans le foreach et en utilisant la fonction getSubIterator sur le RecursiveIteratorIterator au lieu de la variable $ sub. Je pensais que c'était une faute de frappe au début mais c'est la bonne façon! merci Jared.
bchhun
2
Solution géniale. Assez vite aussi!
TaylorOtwell
Merci pour la solution. D'où vient le "id"? Depuis $ outputArray?
trante
Merci, solution très simple, mais je ne sais pas sur les performances ??.
Mahesh.D
comment supprimer l'élément trouvé (pourrait être un sous-tableau) du tableau d'origine?
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);

?>

Réf: http://php.net/manual/en/function.array-filter.php

Prasanthe Bendra
la source
4
C'est une bonne solution si vous voulez rechercher un tableau qui n'a qu'un niveau de profondeur, mais cette question particulière concernait la recherche récursive dans un tableau profond ("la fonction doit être récursive pour descendre au niveau le plus profond").
orrd le
16

Je suis revenu pour publier cette mise à jour pour tous ceux qui ont besoin d'un conseil d'optimisation sur ces réponses, en particulier la grande réponse de John Kugelman ci-dessus.

Sa fonction publiée fonctionne bien, mais j'ai dû optimiser ce scénario pour gérer un ensemble de résultats de 12 000 lignes. La fonction prenait un éternel 8 secondes pour parcourir tous les enregistrements, waaaaaay trop longtemps.

J'avais simplement besoin de la fonction pour arrêter la recherche et revenir lorsque la correspondance était trouvée. Par exemple, si vous recherchez un customer_id, nous savons que nous n'en avons qu'un dans le jeu de résultats et une fois que nous avons trouvé le customer_id dans le tableau multidimensionnel, nous voulons revenir.

Voici la version à vitesse optimisée (et très simplifiée) de cette fonction, pour toute personne dans le besoin. Contrairement aux autres versions, il ne peut gérer qu'une seule profondeur de tableau, ne se répète pas et supprime la fusion de plusieurs résultats.

// 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;       
    } 
}

Cela a réduit la tâche de faire correspondre les 12 000 enregistrements à 1,5 seconde. Toujours très coûteux mais beaucoup plus raisonnable.

stefgosselin
la source
celui-ci est plus rapide que la réponse de Jhon / Jared (0,0009999275207519) vs (0,0020008087158203) .. Eh bien, ce test est spécifique à mon cas et à mon environnement .. Je m'en tiens à cela, merci stefgosselin
Awena
14
if (isset($array[$key]) && $array[$key] == $value)

Une amélioration mineure de la version rapide.

blackmogu
la source
2
En fait, cela l'empêche de lancer des avertissements lorsque la clé n'est pas définie. Pas si mineur! -> + 1'ed.
stefgosselin
2
d'accord, être capable de parcourir le journal des erreurs php pour les erreurs majeures et de ne pas le polluer avec des avertissements est la voie à suivre à mon avis.
codercake le
Ce n'est pas une solution complète et est donc plus une «tentative de répondre à un autre message» et «pas une réponse».
mickmackusa
7

Faites attention aux algorithmes de recherche linéaire (ceux ci-dessus sont linéaires) dans des tableaux multidimensionnels car ils ont aggravé la complexité car sa profondeur augmente le nombre d'itérations nécessaires pour parcourir le tableau entier. Par exemple:

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

prendrait au plus 200 itérations pour trouver ce que vous cherchez (si l'aiguille était à [100] [1]), avec un algorithme approprié.

Les algorithmes linéaires dans ce cas fonctionnent à O (n) (ordre du nombre total d'éléments dans le tableau entier), c'est médiocre, un million d'entrées (par exemple un tableau de 1000x100x10) prendrait en moyenne 500000 itérations pour trouver l'aiguille. Que se passerait-il également si vous décidiez de changer la structure de votre tableau multidimensionnel? Et PHP lancerait un algorithme récursif si votre profondeur était supérieure à 100. L'informatique peut faire mieux:

Dans la mesure du possible, utilisez toujours des objets au lieu de tableaux à plusieurs dimensions:

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

et appliquez une interface et une fonction de comparateur personnalisées pour les trier et les trouver:

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

Vous pouvez utiliser uasort()pour utiliser un comparateur personnalisé, si vous vous sentez aventureux, vous devez implémenter vos propres collections pour vos objets qui peuvent les trier et les gérer (j'étends toujours ArrayObject pour inclure au moins une fonction de recherche).

$arrayObj->uasort("myComp");

Une fois qu'ils sont triés (uasort est O (n log n), ce qui est aussi bon que sur des données arbitraires), la recherche binaire peut effectuer l'opération en temps O (log n), c'est-à-dire qu'un million d'entrées ne prend que ~ 20 itérations pour chercher. Autant que je sache, la recherche binaire de comparateur personnalisé n'est pas implémentée en PHP ( array_search()utilise un ordre naturel qui fonctionne sur les références d'objet et non sur leurs propriétés), vous devrez l'implémenter vous-même comme je le fais.

Cette approche est plus efficace (il n'y a plus de profondeur) et surtout universelle (en supposant que vous appliquez la comparabilité à l'aide d'interfaces) puisque les objets définissent comment ils sont triés, vous pouvez donc recycler le code à l'infini. Beaucoup mieux =)

mbdxgdb2
la source
Cette réponse devrait être correcte. Bien que la méthode de recherche par force brute le fasse, c'est beaucoup moins gourmand en ressources.
Drew le
Il convient de noter que ce que vous suggérez n'a de sens que si vous recherchez plusieurs fois le même tableau. Il faut beaucoup plus de temps pour le trier (O (n log n)) que pour simplement faire une recherche linéaire de la valeur (O (n)). Mais une fois trié, bien sûr, une recherche binaire serait plus rapide.
orrd le
Je dois également ajouter que l'utilisation d'objets au lieu de tableaux peut être une abstraction utile, mais vous pouvez également faire une recherche binaire sur un tableau si le tableau est trié. Vous n'avez pas besoin d'utiliser des objets pour trier un tableau ou pour effectuer une recherche binaire dessus.
orrd le
6

Voici la solution:

<?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
la source
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
la source
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
la source
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
la source
mon cas est différent mais j'ai obtenu un indice de votre réponse.
shyammakwana.me
2

J'avais besoin de quelque chose de similaire, mais pour rechercher un tableau multidimensionnel par valeur ... j'ai pris l'exemple de John et j'ai écrit

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

J'espère que cela aide quelqu'un :)

confiq
la source
2

Ceci est une fonction révisée de celle que John K. a publiée ... Je n'ai besoin de saisir que la clé spécifique du tableau et rien au-dessus.

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
la source
1

Et une autre version qui renvoie la valeur clé de l'élément du tableau dans lequel la valeur est trouvée (pas de récursivité, optimisée pour la vitesse):

// 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;
    } 
  }
}

Merci à tous ceux qui ont posté ici.

Darko Hrgovic
la source
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
la source
2
Pourriez-vous développer cette réponse? Les réponses au code uniquement n'expliquent pas ce que vous faites réellement.
Rich Benner
Veuillez mettre à jour votre question dans le but d'éduquer.
mickmackusa
Ceci est fonctionnel pour ne trouver que la clé, cela fonctionne pour moi.
Giovanny Gonzalez
0

Si vous voulez rechercher un tableau de clés, c'est bien

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

Les clés ne seront pas écrasées car chaque ensemble de valeurs key => sera dans un tableau séparé dans le tableau résultant.
Si vous ne voulez pas de clés en double, utilisez celle-ci

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
la source