PHP: ¿Cómo usar array_filter () para filtrar claves de matriz?

363

La función de devolución de llamada array_filter()solo pasa los valores de la matriz, no las claves.

Si tengo:

$my_array = array("foo" => 1, "hello" => "world");

$allowed = array("foo", "bar");

¿Cuál es la mejor manera de eliminar todas las claves $my_arrayque no están en la $allowedmatriz?

Salida deseada:

$my_array = array("foo" => 1);
maček
fuente
No es una solución, pero otro enfoque que podría ser útil es $b = ['foo' => $a['foo'], 'bar' => $a['bar']]Esto resultará en $b['bar']ser null.
oriadam

Respuestas:

322

PHP 5.6 introdujo un tercer parámetro a array_filter(), flag, que se puede configurar para ARRAY_FILTER_USE_KEYque va a filtrar clave en lugar del valor:

$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed  = ['foo', 'bar'];
$filtered = array_filter(
    $my_array,
    function ($key) use ($allowed) {
        return in_array($key, $allowed);
    },
    ARRAY_FILTER_USE_KEY
);

Claramente, esto no es tan elegante como array_intersect_key($my_array, array_flip($allowed)), pero ofrece la flexibilidad adicional de realizar una prueba arbitraria contra la tecla, por ejemplo, $allowedpodría contener patrones de expresiones regulares en lugar de cadenas simples.

También puede usar ARRAY_FILTER_USE_BOTHpara pasar tanto el valor como la clave a su función de filtro. Aquí hay un ejemplo artificial basado en el primero, pero tenga en cuenta que no recomendaría codificar las reglas de filtrado de $allowedesta manera:

$my_array = ['foo' => 1, 'bar' => 'baz', 'hello' => 'wld'];
$allowed  = ['foo' => true, 'bar' => true, 'hello' => 'world'];
$filtered = array_filter(
    $my_array,
    function ($val, $key) use ($allowed) { // N.b. $val, $key not $key, $val
        return isset($allowed[$key]) && (
            $allowed[$key] === true || $allowed[$key] === $val
        );
    },
    ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']
Richard Turner
fuente
21
Maldición, como autor de esa característica, debería haber buscado esta pregunta ;-)
Ja͢ck
1
Gracias, esto es mejor quearray_intersect
brzuchal
461

Con array_intersect_keyy array_flip:

var_dump(array_intersect_key($my_array, array_flip($allowed)));

array(1) {
  ["foo"]=>
  int(1)
}
Vincent Savard
fuente
1
Sin embargo, ¿tengo curiosidad si esto es más eficiente que mi solución? Definitivamente es más elegante :)
GWW
13
Esta no es una solución general porque exigiría que cada valor sea único. Editar: lo siento .. Leí mal la solución. Pulsar las teclas permitidas es una buena solución (+1)
Mateo
@GWW: No sé si es más eficiente, TBH. @konforce: No estoy seguro de entender tu punto. No puede haber dos claves idénticas en una matriz, por lo que solo devolverá claves en $ my_array que están presentes en $ allow.
Vincent Savard el
1
O simplemente use ARRAY_FILTER_USE_KEY: P
Julien Palard
1
¿Por qué usar array_flip? Simplemente defina las $allowedteclas con:allowed = array ( 'foo' => 1, 'bar' => 1 );
Yuval A.
43

Necesitaba hacer lo mismo, pero con un array_filterteclado más complejo .

Así es como lo hice, usando un método similar.

// Filter out array elements with keys shorter than 4 characters
$a = array(
  0      => "val 0", 
  "one"  => "val one", 
  "two"  => "val two", 
  "three"=> "val three", 
  "four" => "val four", 
  "five" => "val five", 
  "6"    => "val 6"
); 

$f = array_filter(array_keys($a), function ($k){ return strlen($k)>=4; }); 
$b = array_intersect_key($a, array_flip($f));
print_r($b);

Esto genera el resultado:

Array
(
    [three] => val three
    [four] => val four
    [five] => val five
)
Christopher
fuente
8

Aquí hay una solución más flexible que usa un cierre:

$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
$result = array_flip(array_filter(array_flip($my_array), function ($key) use ($allowed)
{
    return in_array($key, $allowed);
}));
var_dump($result);

Salidas:

array(1) {
  'foo' =>
  int(1)
}

Entonces, en la función, puede hacer otras pruebas específicas.

Bobina
fuente
1
No llamaría exactamente esto "más flexible"; también se siente mucho menos directo que la solución aceptada.
maček
Estoy de acuerdo. Sería más flexible si la condición fuera más compleja.
Bobina el
1
De paso, para otros usuarios: esta solución no trata el caso de que $ my_array tenga valores duplicados o valores que no sean enteros ni cadenas. Entonces no usaría esta solución.
user23127
2
Estoy de acuerdo en que esto es más flexible, ya que le permite cambiar la lógica del filtro. Por ejemplo, utilicé una matriz de claves no permitidas y simplemente devolví! In_array ($ clave, $ no permitido).
nfplee
5

Si está buscando un método para filtrar una matriz por una cadena que aparece en las claves, puede usar:

$mArray=array('foo'=>'bar','foo2'=>'bar2','fooToo'=>'bar3','baz'=>'nope');
$mSearch='foo';
$allowed=array_filter(
    array_keys($mArray),
    function($key) use ($mSearch){
        return stristr($key,$mSearch);
    });
$mResult=array_intersect_key($mArray,array_flip($allowed));

El resultado de print_r($mResult)es

Array ( [foo] => bar [foo2] => bar2 [fooToo] => bar3 )

Una adaptación de esta respuesta que admite expresiones regulares

function array_preg_filter_keys($arr, $regexp) {
  $keys = array_keys($arr);
  $match = array_filter($keys, function($k) use($regexp) {
    return preg_match($regexp, $k) === 1;
  });
  return array_intersect_key($arr, array_flip($match));
}

$mArray = array('foo'=>'yes', 'foo2'=>'yes', 'FooToo'=>'yes', 'baz'=>'nope');

print_r(array_preg_filter_keys($mArray, "/^foo/i"));

Salida

Array
(
    [foo] => yes
    [foo2] => yes
    [FooToo] => yes
)
Nicolas Zimmer
fuente
gracias por tu respuesta. Le diría que usar stristrdentro del "trabajo" de la función es hacer algunas suposiciones para el usuario final. Quizás sería mejor permitir que el usuario pase una expresión regular; esto les daría una mayor flexibilidad sobre ciertas cosas como anclas, los límites de palabra y de mayúsculas y minúsculas, etc.
Maček
Agregué
1
Ciertamente tienes razón, maček, ese es un enfoque más versátil para los usuarios que se sienten cómodos con la expresión regular. Gracias.
Nicolas Zimmer
5

Cómo obtener la clave actual de una matriz cuando se usa array_filter

Independientemente de cómo me gusta la solución de Vincent para el problema de Maček, en realidad no se usa array_filter. Si viniste aquí desde un motor de búsqueda, tal vez estabas buscando algo como esto ( PHP> = 5.3 ):

$array = ['apple' => 'red', 'pear' => 'green'];
reset($array); // Unimportant here, but make sure your array is reset

$apples = array_filter($array, function($color) use ($&array) {
  $key = key($array);
  next($array); // advance array pointer

  return key($array) === 'apple';
}

Pasa la matriz que está filtrando como referencia a la devolución de llamada. Como array_filterno itera convencionalmente sobre la matriz aumentando su puntero interno público, debe avanzarlo usted mismo.

Lo importante aquí es que debe asegurarse de que su matriz se restablezca, de lo contrario, podría comenzar justo en el medio.

En PHP> = 5.4 puede hacer que la devolución de llamada sea aún más corta:

$apples = array_filter($array, function($color) use ($&array) {
  return each($array)['key'] === 'apple';
}
gripe
fuente
3

Aquí hay una alternativa menos flexible usando unset () :

$array = array(
    1 => 'one',
    2 => 'two',
    3 => 'three'
);
$disallowed = array(1,3);
foreach($disallowed as $key){
    unset($array[$key]);
}

El resultado de print_r($array)ser:

Array
(
    [2] => two
)

Esto no es aplicable si desea mantener los valores filtrados para su uso posterior pero más ordenado, si está seguro de que no.

Alastair
fuente
1
Debe verificar si la clave $ clave existe en $ array antes de desarmar.
Jarek Jakubowski
3
@JarekJakubowski, no es necesario verificar si existe una clave de matriz cuando se usa unset(). No se emiten advertencias si la clave no existe.
Christopher
3

A partir de PHP 5.6, puede usar la ARRAY_FILTER_USE_KEYbandera en array_filter:

$result = array_filter($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
}, ARRAY_FILTER_USE_KEY);


De lo contrario, puede usar esta función ( desde TestDummy ):

function filter_array_keys(array $array, $callback)
{
    $matchedKeys = array_filter(array_keys($array), $callback);

    return array_intersect_key($array, array_flip($matchedKeys));
}

$result = filter_array_keys($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
});


Y aquí hay una versión aumentada mía, que acepta una devolución de llamada o directamente las claves:

function filter_array_keys(array $array, $keys)
{
    if (is_callable($keys)) {
        $keys = array_filter(array_keys($array), $keys);
    }

    return array_intersect_key($array, array_flip($keys));
}

// using a callback, like array_filter:
$result = filter_array_keys($my_array, function ($k) use ($allowed) {
    return in_array($k, $allowed);
});

// or, if you already have the keys:
$result = filter_array_keys($my_array, $allowed));


Por último, pero no menos importante, también puede usar un simple foreach:

$result = [];
foreach ($my_array as $key => $value) {
    if (in_array($key, $allowed)) {
        $result[$key] = $value;
    }
}
Gras Double
fuente
1

Tal vez una exageración si la necesita solo una vez, pero puede usar la biblioteca YaLinqo * para filtrar colecciones (y realizar cualquier otra transformación). Esta biblioteca permite realizar consultas similares a SQL en objetos con sintaxis fluida. Su wherefunción acepta una devolución de llamada con dos argumentos: un valor y una clave. Por ejemplo:

$filtered = from($array)
    ->where(function ($v, $k) use ($allowed) {
        return in_array($k, $allowed);
    })
    ->toArray();

(La wherefunción devuelve un iterador, por lo que si solo necesita iterar foreachsobre la secuencia resultante una vez, ->toArray()puede eliminarse).

* desarrollado por mí

Athari
fuente
1

función de filtro de matriz de php:

array_filter ( $array, $callback_function, $flag )

$ array: es la matriz de entrada

$ callback_function: la función de devolución de llamada que se utilizará, si la función de devolución de llamada devuelve verdadero , el valor actual de la matriz se devuelve a la matriz de resultados.

$ flag: es un parámetro opcional , determinará qué argumentos se envían a la función de devolución de llamada. Si este parámetro está vacío, la función de devolución de llamada tomará los valores de la matriz como argumento. Si desea enviar una clave de matriz como argumento, use $ flag como ARRAY_FILTER_USE_KEY . Si desea enviar claves y valores, debe usar $ flag como ARRAY_FILTER_USE_BOTH .

Por ejemplo: considere una matriz simple

$array = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);

Si desea filtrar la matriz en función de la clave de la matriz , necesitamos usar ARRAY_FILTER_USE_KEY como tercer parámetro de la función de matriz array_filter.

$get_key_res = array_filter($array,"get_key",ARRAY_FILTER_USE_KEY );

Si desea filtrar la matriz en función de la clave de la matriz y el valor de la matriz , necesitamos utilizar ARRAY_FILTER_USE_BOTH como tercer parámetro de la función de matriz array_filter.

$get_both = array_filter($array,"get_both",ARRAY_FILTER_USE_BOTH );

Funciones de devolución de llamada de muestra:

 function get_key($key)
 {
    if($key == 'a')
    {
        return true;
    } else {
        return false;
    }
}
function get_both($val,$key)
{
    if($key == 'a' && $val == 1)
    {
        return true;
    }   else {
        return false;
    }
}

Saldrá

Output of $get_key is :Array ( [a] => 1 ) 
Output of $get_both is :Array ( [a] => 1 ) 
príncipe jose
fuente
0

Con esta función puede filtrar una matriz multidimensional

function filter_array_keys($array,$filter_keys=array()){

    $l=array(&$array);
    $c=1;
    //This first loop will loop until the count var is stable//
    for($r=0;$r<$c;$r++){
        //This loop will loop thru the child element list//
        $keys = array_keys($l[$r]);

        for($z=0;$z<count($l[$r]);$z++){
            $object = &$l[$r][$keys[$z]];

            if(is_array($object)){
                $i=0;
                $keys_on_array=array_keys($object);
                $object=array_filter($object,function($el) use(&$i,$keys_on_array,$filter_keys){
                    $key = $keys_on_array[$i];
                    $i++;

                    if(in_array($key,$filter_keys) || is_int($key))return false;                
                    return true;                        
                });
            }

            if(is_array($l[$r][$keys[$z]])){
                $l[] = &$l[$r][$keys[$z]];
                $c++;
            }//IF           
        }//FOR
    }//FOR  

    return $l[0];

}
usuario1220713
fuente
0
// Filter out array elements with keys shorter than 4 characters 
// By using Anonymous function with  Closure...     

function comparison($min)
{
   return function($item) use ($min) { 
      return strlen($item) >= $min;   
   }; 
}

$input = array(
  0      => "val 0",
  "one"  => "val one",
  "two"  => "val two",
  "three"=> "val three",
  "four" => "val four",  
  "five" => "val five",    
  "6"    => "val 6"    
);

$output = array_filter(array_keys($input), comparison(4));    

print_r($output);

Salida de ejecución

ZOB
fuente
0

¿Solución ingenua y fea (pero parece ser más rápida)?

Solo probé esto en php 7.3.11, pero un bucle feo parece ejecutarse en aproximadamente un tercio del tiempo. Resultados similares en una matriz con unos pocos cientos de claves. Micro-optimización, probablemente no útil en RW, pero lo encontró sorprendente e interesante:

$time = microtime(true);
$i = 100000;
while($i) {
    $my_array = ['foo' => 1, 'hello' => 'world'];
    $allowed  = ['foo', 'bar'];
    $filtered = array_filter(
        $my_array,
        function ($key) use ($allowed) {
            return in_array($key, $allowed);
        },
        ARRAY_FILTER_USE_KEY
    );
    $i--;
}
print_r($filtered);
echo microtime(true) - $time . ' on array_filter';

// 0.40600109100342 on array_filter
$time2 = microtime(true);
$i2 = 100000;
while($i2) {
    $my_array2 = ['foo' => 1, 'hello' => 'world'];
    $allowed2  = ['foo', 'bar'];
    $filtered2 = [];
    foreach ($my_array2 as $k => $v) {
        if (in_array($k, $allowed2)) $filtered2[$k] = $v;
    }
    $i2--;
}
print_r($filtered2);
echo microtime(true) - $time2 . ' on ugly loop';
// 0.15677785873413 on ugly loop
sepiariver
fuente
-1
$elements_array = ['first', 'second'];

función para eliminar algunos elementos de la matriz

function remove($arr, $data) {
    return array_filter($arr, function ($element) use ($data) {
        return $element != $data;
    });
}

llamar e imprimir

print_r(remove($elements_array, 'second'));

el resultado Array ( [0] => first )

Abdallah Awwad Alkhwaldah
fuente
La pregunta era sobre el filtrado de claves de matriz, no valores.
poletaew