Como pesquisar por key => value em uma matriz multidimensional em PHP

147

Existe alguma maneira rápida de obter todos os subarrays em que um par de valores-chave foi encontrado em uma matriz multidimensional? Não sei dizer quão profunda será a matriz.

Matriz de exemplo simples:

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

Quando eu procuro key = name e value = "cat 1", a função deve retornar:

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

Acho que a função precisa ser recursiva para chegar ao nível mais profundo.


fonte

Respostas:

217

Código:

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

Resultado:

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

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

)

Se a eficiência é importante, você pode escrevê-la para que todas as chamadas recursivas armazenem seus resultados na mesma $resultsmatriz temporária, em vez de mesclar matrizes, da seguinte forma:

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

A chave é aquela que search_rpega seu quarto parâmetro por referência e não por valor; oe comercial &é crucial.

FYI: Se você tiver uma versão mais antiga do PHP, então você tem que especificar a parte passagem por referência na chamada para search_r, em vez de na sua declaração. Ou seja, a última linha se torna search_r($subarray, $key, $value, &$results).

John Kugelman
fonte
2
@JohnKugelman O erro de resposta "eficiente" não será exibido se $keyele não existir na matriz? Não seria melhor fazer if (array_key_exists($key, $array) && $array[$key] == $value) {?
Chase
1
@JohnKugelman Esta função funciona bem, mas em algum momento eu tenho o $valueque é nulle a função não funciona ... array empty... Como ter uma matriz mesmo se $value= null? gosta search($array, 'id', null)?
Zagloo 01/04
71

Que tal a versão SPL ? Isso poupará algumas digitações:

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

O que é bom é que basicamente o mesmo código irá percorrer um diretório para você, usando um RecursiveDirectoryIterator em vez de um RecursiveArrayIterator. SPL é o roxor.

A única chatice sobre SPL é que ela está mal documentada na web. Mas vários livros sobre PHP abordam alguns detalhes úteis, principalmente o Pro PHP; e você provavelmente pode pesquisar no Google para obter mais informações também.

jared
fonte
Isso funciona como um encanto e planejo usá-lo novamente para problemas semelhantes: D A única parte estranha está no foreach e usando a função getSubIterator no RecursiveIteratorIterator em vez da variável $ sub. Eu pensei que era um erro de digitação no começo, mas é o caminho certo! obrigado Jared.
bchhun
2
Solução impressionante. Muito rápido também!
TaylorOtwell
Obrigado pela solução. Onde obtemos o "id"? De $ outputArray?
trante
Obrigado, solução muito direta, mas não sei sobre desempenho ??.
perfil completo de Mahesh
como desarmar o elemento encontrado (poderia ser um sub-array) da matriz original?
precisa saber é o seguinte
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
fonte
4
Essa é uma boa solução se você deseja pesquisar uma matriz com apenas um nível de profundidade, mas essa pergunta específica era sobre pesquisar recursivamente em uma matriz profunda ("a função precisa ser recursiva para chegar ao nível mais profundo").
orrd 18/06/2015
16

Voltei para publicar esta atualização para qualquer pessoa que precise de uma dica de otimização nessas respostas, especialmente a excelente resposta de John Kugelman acima.

Sua função postada funciona bem, mas tive que otimizar esse cenário para lidar com um conjunto de resultados de 12.000 linhas. A função estava demorando 8 segundos eternos para passar por todos os registros, tempo demais.

Eu simplesmente precisava da função para PARAR a busca e retornar quando a correspondência era encontrada. Ou seja, se estiver procurando por um customer_id, sabemos que só temos um no conjunto de resultados e, uma vez que encontramos o customer_id na matriz multidimensional, queremos retornar.

Aqui está a versão otimizada para velocidade (e muito simplificada) dessa função, para quem precisa. Diferente de outras versões, ele pode lidar apenas com uma profundidade de matriz, não recursa e acaba com a mesclagem de vários resultados.

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

Isso reduziu a tarefa de corresponder os 12.000 registros a 1,5 segundos. Ainda muito caro, mas muito mais razoável.

stefgosselin
fonte
este é mais rápido do que Jhon / resposta de Jared (,0009999275207519) vs (,0020008087158203) .. Bem este teste é específico para o meu caso e meio ambiente .. Im aderindo com isto, graças stefgosselin
Awena
14
if (isset($array[$key]) && $array[$key] == $value)

Uma pequena melhoria para a versão rápida.

blackmogu
fonte
2
Na verdade, isso impede que seja emitido avisos quando a chave não está definida. Não é tão menor! -> marcou com +1.
stefgosselin
2
concordou, ser capaz de realmente olhar através do log de erros do php para erros maiores e não poluí-lo com avisos é o caminho a percorrer na minha opinião.
codercake
Esta não é uma solução completa e, portanto, é mais uma "Tentativa de responder a outra postagem" e "Não é uma resposta".
Mckmackusa
7

Tenha cuidado com os algoritmos de pesquisa linear (os acima são lineares) em matrizes multidimensionais, pois elas aumentam a complexidade, pois sua profundidade aumenta o número de iterações necessárias para percorrer toda a matriz. Por exemplo:

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

seriam necessárias no máximo 200 iterações para encontrar o que você estava procurando (se a agulha estivesse em [100] [1]), com um algoritmo adequado.

Os algoritmos lineares, neste caso, são executados em O (n) (número total de elementos da ordem em todo o array); isso é ruim; um milhão de entradas (por exemplo, um array de 1000x100x10) levaria em média 500.000 iterações para encontrar a agulha. Além disso, o que aconteceria se você decidisse alterar a estrutura de sua matriz multidimensional? E o PHP lançaria um algoritmo recursivo se sua profundidade fosse superior a 100. A ciência da computação pode fazer melhor:

Sempre que possível, sempre use objetos em vez de várias matrizes dimensionais:

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

e aplique uma interface e função comparadora personalizada para classificá-las e localizá-las:

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

Você pode usar uasort()para utilizar um comparador personalizado; se estiver se aventurando, deve implementar suas próprias coleções para seus objetos que podem classificá-los e gerenciá-los (eu sempre estendo o ArrayObject para incluir no mínimo uma função de pesquisa).

$arrayObj->uasort("myComp");

Depois de ordenadas (uasort é O (n log n), o que é tão bom quanto obtém dados arbitrários), a pesquisa binária pode fazer a operação em O (log n), ou seja, um milhão de entradas leva apenas 20 iterações para procurar. Tanto quanto sei, a pesquisa binária do comparador personalizado não está implementada no PHP (array_search() usa ordenação natural que funciona em referências a objetos e não em suas propriedades), você teria que implementar isso como você.

Essa abordagem é mais eficiente (não existe mais profundidade) e, mais importante, universal (supondo que você imponha comparabilidade usando interfaces), pois os objetos definem como eles são classificados, para que você possa reciclar o código infinitamente. Muito melhor =)

mbdxgdb2
fonte
Esta resposta deve estar correta. Embora o método de busca por força bruta o faça, isso consome muito menos recursos.
Drew
Note-se que o que você está sugerindo só faz sentido se você estiver pesquisando a mesma matriz várias vezes. Leva muito mais tempo para resolver o problema de classificá-lo (O (n log n)) do que simplesmente fazer uma pesquisa linear pelo valor (O (n)). Mas uma vez que é classificada, com certeza, uma pesquisa binária seria mais rápida.
orrd 18/06/2015
Devo acrescentar também que o uso de objetos em vez de matrizes pode ser uma abstração útil, mas você também pode fazer uma pesquisa binária em uma matriz se a matriz estiver classificada. Você não precisa usar objetos para classificar uma matriz ou fazer uma pesquisa binária nela.
orrd 18/06/2015
6

Aqui está a solução:

<?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
fonte
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
fonte
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
fonte
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
fonte
meu caso é diferente, mas recebi dicas da sua resposta.
Shyammakwana.me
2

Eu precisava de algo semelhante, mas para procurar matriz multidimensional por valor ... tomei o exemplo de John e escrevi

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

Espero que ajude alguém :)

confiq
fonte
2

Esta é uma função revisada daquela que John K. postou ... Eu preciso pegar apenas a chave específica na matriz e nada acima dela.

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
fonte
1

E outra versão que retorna o valor da chave do elemento da matriz em que o valor é encontrado (sem recursão, otimizado para velocidade):

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

Obrigado a todos que postaram aqui.

Darko Hrgovic
fonte
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
fonte
2
Você poderia expandir esta resposta? As respostas somente em código não explicam o que você está realmente fazendo.
Rich Benner
Atualize sua pergunta com a intenção de educar.
Mckmackusa
Isso é funcional apenas para encontrar a chave, isso funciona para mim.
Giovanny Gonzalez
0

Se você deseja procurar por uma série de chaves, isso é bom

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

As chaves não serão substituídas porque cada conjunto de chave => valores estará em uma matriz separada na matriz resultante.
Se você não quiser chaves duplicadas, use esta

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
fonte