Cómo buscar por clave => valor en una matriz multidimensional en PHP

147

¿Hay alguna forma rápida de obtener todas las submatrices donde se encontró un par de valores clave en una matriz multidimensional? No puedo decir qué tan profunda será la matriz.

Matriz de ejemplo simple:

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

Cuando busco key = name y value = "cat 1", la función debería devolver:

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

Supongo que la función tiene que ser recursiva para llegar al nivel más profundo.


fuente

Respuestas:

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

Salida:

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

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

)

Si la eficiencia es importante, puede escribirla para que todas las llamadas recursivas almacenen sus resultados en la misma $resultsmatriz temporal en lugar de fusionar las matrices juntas, de esta manera:

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 clave es que search_rtoma su cuarto parámetro por referencia en lugar de por valor; El comercial &es crucial.

FYI: Si tiene una versión anterior de PHP, debe especificar la parte de paso por referencia en la llamada en search_rlugar de en su declaración. Es decir, la última línea se convierte search_r($subarray, $key, $value, &$results).

John Kugelman
fuente
2
@JohnKugelman ¿No se solucionará el error de respuesta "eficiente" si $keyno existe en la matriz? ¿No sería mejor hacerlo if (array_key_exists($key, $array) && $array[$key] == $value) {?
Chase
1
@JohnKugelman Esta función funciona bien pero a veces tengo mi $valueque es nully la función no funciona ... array empty... ¿Cómo tener una matriz incluso si $value= null? como search($array, 'id', null)?
Zagloo 01 de
71

¿Qué tal la versión SPL en su lugar? Te ahorrará algo de tipeo:

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

Lo bueno es que básicamente el mismo código iterará a través de un directorio para usted, utilizando un RecursiveDirectoryIterator en lugar de un RecursiveArrayIterator. SPL es el roxor.

Lo único malo de SPL es que está mal documentado en la web. Pero varios libros de PHP tienen algunos detalles útiles, particularmente Pro PHP; y probablemente también puedas buscar en Google más información.

Jared
fuente
Esto funciona de maravilla y planeo usarlo nuevamente para problemas similares: D La única parte extraña está en el foreach y usando la función getSubIterator en RecursiveIteratorIterator en lugar de la variable $ sub. Al principio pensé que era un error tipográfico, ¡pero es la forma correcta! gracias Jared
bchhun
2
Impresionante solución. Bastante rápido también!
TaylorOtwell
Gracias por la solucion. ¿Dónde obtenemos el "id"? Desde $ outputArray?
trante
Gracias, solución muy sencilla, pero no sé sobre el rendimiento.
Mahesh.D
¿Cómo desarmar el elemento encontrado (podría ser una sub-matriz) de la matriz original?
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
fuente
44
Esta es una buena solución si desea buscar una matriz que tenga solo un nivel de profundidad, pero esta pregunta en particular fue sobre la búsqueda recursiva en una matriz profunda ("la función tiene que ser recursiva para llegar al nivel más profundo").
orden
16

Volví a publicar esta actualización para cualquiera que necesite un consejo de optimización en estas respuestas, particularmente la gran respuesta de John Kugelman arriba.

Su función publicada funciona bien, pero tuve que optimizar este escenario para manejar un conjunto de resultados de 12 000 filas. La función estaba tardando 8 segundos eternos en revisar todos los registros, muuuuuuuuuuucho mucho tiempo.

Simplemente necesitaba la función para DETENER la búsqueda y regresar cuando se encontró coincidencia. Es decir, si busca un customer_id, sabemos que solo tenemos uno en el conjunto de resultados y una vez que encontramos el customer_id en la matriz multidimensional, queremos volver.

Aquí está la versión de esta función con velocidad optimizada (y mucho más simplificada), para cualquier persona que lo necesite. A diferencia de otras versiones, solo puede manejar una sola profundidad de matriz, no se repite y elimina la fusión de múltiples 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;       
    } 
}

Esto redujo la tarea de hacer coincidir los 12 000 registros con 1,5 segundos. Sigue siendo muy costoso pero mucho más razonable.

Stefgosselin
fuente
esta es más rápida que la respuesta de Jhon / Jared (0.0009999275207519) vs (0.0020008087158203) ... Bueno, esta prueba es específica para mi caso y entorno ... Estoy apegado a esto, gracias stefgosselin
Awena
14
if (isset($array[$key]) && $array[$key] == $value)

Una mejora menor a la versión rápida.

blackmogu
fuente
2
En realidad esto evita que arroje advertencias cuando la clave no está configurada. ¡No tan menor! -> + 1'ed.
stefgosselin
2
De acuerdo, en mi opinión, poder mirar el registro de errores de php en busca de errores importantes y no contaminarlo con advertencias es el camino a seguir.
codercake
Esta no es una solución completa y, por lo tanto, es más un "intento de responder a otra publicación" y "no una respuesta".
mickmackusa
7

Tenga cuidado con los algoritmos de búsqueda lineal (los anteriores son lineales) en matrices multidimensionales, ya que han agravado la complejidad ya que su profundidad aumenta el número de iteraciones necesarias para atravesar toda la matriz. P.ej:

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

tomaría como máximo 200 iteraciones para encontrar lo que está buscando (si la aguja estuviera en [100] [1]), con un algoritmo adecuado.

Los algoritmos lineales en este caso funcionan en O (n) (ordenar el número total de elementos en toda la matriz), esto es pobre, un millón de entradas (por ejemplo, una matriz de 1000x100x10) tomaría en promedio 500,000 iteraciones para encontrar la aguja. Además, ¿qué pasaría si decidiera cambiar la estructura de su matriz multidimensional? Y PHP lanzaría un algoritmo recursivo si su profundidad fuera más de 100. La informática puede mejorar:

Siempre que sea posible, use siempre objetos en lugar de matrices multidimensionales:

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

y aplique una interfaz y función de comparación personalizadas para ordenarlas y encontrarlas:

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

Puede uasort()utilizar un comparador personalizado; si se siente aventurero, debe implementar sus propias colecciones para sus objetos que puedan ordenarlos y administrarlos (siempre extiendo ArrayObject para incluir una función de búsqueda como mínimo).

$arrayObj->uasort("myComp");

Una vez que se ordenan (uasort es O (n log n), que es tan bueno como se supera a los datos arbitrarios), la búsqueda binaria puede hacer la operación en tiempo O (log n), es decir, un millón de entradas solo requieren ~ 20 iteraciones para buscar. Que yo sepa, la búsqueda binaria de comparación personalizada no está implementada en PHP (array_search() usa un orden natural que funciona en referencias de objetos, no en sus propiedades), tendría que implementar esto usted mismo como lo hago yo.

Este enfoque es más eficiente (ya no hay profundidad) y lo más importante es universal (suponiendo que imponga la comparabilidad usando interfaces) ya que los objetos definen cómo se ordenan, por lo que puede reciclar el código infinitamente. Mucho mejor =)

mbdxgdb2
fuente
Esta respuesta debe ser correcta. Aunque el método de búsqueda de fuerza bruta lo hará, esto requiere mucho menos recursos.
Dibujó el
Cabe señalar que lo que está sugiriendo solo tiene sentido si está buscando la misma matriz muchas veces. Lleva mucho más tiempo pasar por el problema de ordenarlo (O (n log n)) que simplemente hacer una búsqueda lineal del valor (O (n)). Pero una vez que está ordenado, claro, entonces una búsqueda binaria sería más rápida.
orden
También debo agregar que usar objetos en lugar de matrices puede ser una abstracción útil, pero también podría hacer una búsqueda binaria en una matriz si la matriz está ordenada. No necesita usar objetos para ordenar una matriz o hacer una búsqueda binaria en ella.
orden
6

Aquí hay solución:

<?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
fuente
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
fuente
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
fuente
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
fuente
mi caso es diferente pero recibí una pista de tu respuesta.
shyammakwana.me
2

Necesitaba algo similar, pero para buscar una matriz multidimensional por valor ... Tomé el ejemplo de John y escribí

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 ayude a alguien :)

confiq
fuente
2

Esta es una función revisada de la que John K. publicó ... Necesito tomar solo la clave específica en la matriz y nada por encima.

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

Y otra versión que devuelve el valor clave del elemento de matriz en el que se encuentra el valor (sin recursividad, optimizado para la velocidad):

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

Gracias a todos los que publicaron aquí.

Darko Hrgovic
fuente
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
fuente
2
¿Podría ampliar esta respuesta? Las respuestas de solo código no explican lo que realmente estás haciendo.
Rich Benner
Actualice su pregunta con la intención de educar.
mickmackusa
Esto es funcional solo para encontrar Key, esto funciona para mí.
Giovanny Gonzalez el
0

Si desea buscar un conjunto de claves, esto es bueno

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

Las claves no se sobrescribirán porque cada conjunto de valores clave => estará en una matriz separada en la matriz resultante.
Si no quieres claves duplicadas, usa 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
fuente