PHP: Как использовать array_filter () для фильтрации ключей массива?

363

Функция обратного вызова in array_filter()передает только значения массива, а не ключи.

Если у меня есть:

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

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

Какой лучший способ удалить все ключи $my_array, которых нет в $allowedмассиве?

Желаемый вывод:

$my_array = array("foo" => 1);
Maček
источник
Не решение, а другой подход, который может быть полезен - $b = ['foo' => $a['foo'], 'bar' => $a['bar']]это приведет к тому, $b['bar']что null.
Ориадам

Ответы:

322

PHP 5.6 введен третий параметр array_filter(), flag, что вы можете установить для ARRAY_FILTER_USE_KEYфильтрации по ключу вместо значения:

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

Ясно, что это не так элегантно, как array_intersect_key($my_array, array_flip($allowed)), но он предлагает дополнительную гибкость выполнения произвольного теста с ключом, например, $allowedможет содержать шаблоны регулярных выражений вместо простых строк.

Вы также можете использовать, ARRAY_FILTER_USE_BOTHчтобы и значение, и ключ передавались в функцию фильтра. Вот надуманный пример, основанный на первом, но обратите внимание, что я бы не рекомендовал использовать правила фильтрации кодирования $allowedтаким способом:

$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']
Ричард Тернер
источник
21
Черт, как автор этой функции, я должен был искать этот вопрос ;-)
Ja͢ck
1
Спасибо, это лучше, чемarray_intersect
brzuchal
461

С array_intersect_keyи array_flip:

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

array(1) {
  ["foo"]=>
  int(1)
}
Винсент Савард
источник
1
Мне любопытно, если это более эффективно, чем мое решение, хотя? Это определенно более элегантно :)
GWW
13
Это не общее решение, потому что оно потребует уникальности каждого значения. Изменить: извините .. Я неправильно понял решение. Нажатие на разрешенные ключи - хорошее решение (+1)
Мэтью
@GWW: Я не знаю, если это более эффективно, TBH. @konforce: я не уверен, чтобы понять вашу точку зрения. В массиве не может быть двух одинаковых ключей, поэтому он будет возвращать только ключи в $ my_array, которые присутствуют в $ allow.
Винсент Савард
1
Или просто используйте ARRAY_FILTER_USE_KEY: P
Жюльен Палар
1
Зачем использовать array_flip? Просто определите с $allowedпомощью клавиш:allowed = array ( 'foo' => 1, 'bar' => 1 );
Ювал А.
43

Мне нужно было сделать то же самое, но с более сложными array_filterклавишами.

Вот как я это сделал, используя аналогичный метод.

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

Это выводит результат:

Array
(
    [three] => val three
    [four] => val four
    [five] => val five
)
Кристофер
источник
8

Вот более гибкое решение с использованием замыкания:

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

Выходы:

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

Таким образом, в функции вы можете выполнять другие конкретные тесты.

змеевик
источник
1
Я бы точно не назвал это «более гибким»; оно кажется гораздо менее простым, чем принятое решение.
Мачек
Согласен. Это было бы более гибким, если бы условие было более сложным.
Катушка
1
Просто мимоходом для других пользователей: Это решение не касается случая, когда $ my_array имеет повторяющиеся значения или значения, которые не являются целыми числами или строками. Поэтому я бы не стал использовать это решение.
user23127
2
Я согласен, что это более гибко, поскольку позволяет изменить логику фильтра. Например, я использовал массив запрещенных ключей и просто возвратил! In_array ($ key, $ disallowed).
nfplee
5

Если вы ищете метод для фильтрации массива по строке, встречающейся в ключах, вы можете использовать:

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

Результатом print_r($mResult)является

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

Адаптация этого ответа, которая поддерживает регулярные выражения

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

Вывод

Array
(
    [foo] => yes
    [foo2] => yes
    [FooToo] => yes
)
Николас Циммер
источник
спасибо за Ваш ответ. Я хотел бы представить вам, что использование stristrв рамках «работы» функции делает некоторые предположения для конечного пользователя. Возможно, было бы лучше позволить пользователю передавать регулярное выражение; это дало бы им больше гибкости по отношению к определенным вещам, таким как привязки, границы слов, чувствительность к регистру и т. д.
maček
Я добавил адаптацию вашего ответа, которая может помочь другим людям
maček
1
Вы, безусловно, правы, maček, это более универсальный подход для пользователей, которым комфортно с regex. Спасибо.
Николас Циммер
5

Как получить текущий ключ массива при использовании array_filter

Независимо от того, как мне нравится решение Винсента для проблемы Мачека, оно на самом деле не используется array_filter. Если вы пришли сюда из поисковой системы, вы, возможно, искали что-то вроде этого ( 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';
}

Он передает массив, который вы фильтруете, как ссылку на обратный вызов. Так array_filterкак условно не перебирать массив, увеличивая его публичный внутренний указатель, вы должны продвигать его самостоятельно.

Здесь важно то, что вам нужно убедиться, что ваш массив сброшен, иначе вы можете начать прямо с середины.

В PHP> = 5.4 вы можете сделать обратный вызов еще короче:

$apples = array_filter($array, function($color) use ($&array) {
  return each($array)['key'] === 'apple';
}
грипп
источник
3

Вот менее гибкая альтернатива с использованием unset () :

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

Результат print_r($array)бытия:

Array
(
    [2] => two
)

Это не применимо, если вы хотите сохранить отфильтрованные значения для последующего использования, но аккуратнее, если вы уверены, что это не так.

Аластер
источник
1
Вы должны проверить, существует ли ключ $ key в массиве $ перед выполнением unset.
Ярек Якубовски,
3
@JarekJakubowski вам не нужно проверять, существует ли ключ массива при использовании unset(). Предупреждения не выдаются, если ключ не существует.
Кристофер
3

Начиная с PHP 5.6, вы можете использовать ARRAY_FILTER_USE_KEYфлаг в array_filter:

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


В противном случае вы можете использовать эту функцию ( из 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);
});


А вот моя дополненная версия, которая принимает обратный вызов или непосредственно ключи:

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


И последнее, но не менее важное: вы также можете использовать простое foreach:

$result = [];
foreach ($my_array as $key => $value) {
    if (in_array($key, $allowed)) {
        $result[$key] = $value;
    }
}
Гра Двухместный
источник
1

Возможно, это излишне, если вам это нужно только один раз, но вы можете использовать библиотеку YaLinqo * для фильтрации коллекций (и выполнения любых других преобразований). Эта библиотека позволяет выполнять SQL-подобные запросы к объектам с свободным синтаксисом. Его whereфункция принимает обратный вызов с двумя аргументами: значением и ключом. Например:

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

( whereФункция возвращает итератор, поэтому, если вам нужно только выполнить итерацию foreachпо полученной последовательности один раз, ->toArray()ее можно удалить.)

* разработано мной

Асария
источник
1

функция фильтра массива из php:

array_filter ( $array, $callback_function, $flag )

$ array - это входной массив

$ callback_function - Используемая функция обратного вызова. function Если функция обратного вызова возвращает true , текущее значение из массива возвращается в массив результатов.

$ flag - это необязательный параметр , он определяет, какие аргументы отправляются в функцию обратного вызова. Если этот параметр пуст, то функция обратного вызова будет принимать значения массива в качестве аргумента. Если вы хотите отправить ключ массива в качестве аргумента, используйте $ flag как ARRAY_FILTER_USE_KEY . Если вы хотите отправить ключи и значения, вы должны использовать $ flag как ARRAY_FILTER_USE_BOTH .

Например: рассмотрим простой массив

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

Если вы хотите фильтровать массив на основе ключа массива , нам нужно использовать ARRAY_FILTER_USE_KEY в качестве третьего параметра функции массива array_filter.

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

Если вы хотите фильтровать массив на основе ключа массива и значения массива , нам нужно использовать ARRAY_FILTER_USE_BOTH в качестве третьего параметра функции массива array_filter.

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

Пример функции обратного вызова:

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

Будет выводить

Output of $get_key is :Array ( [a] => 1 ) 
Output of $get_both is :Array ( [a] => 1 ) 
принц хосе
источник
0

С помощью этой функции вы можете фильтровать многомерный массив

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

}
user1220713
источник
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);

Выход из пробега

ZOB
источник
0

Наивное и некрасивое (но, кажется, более быстрое) решение?

Только попробовал это в php 7.3.11, но уродливый цикл, кажется, выполняется примерно в трети времени. Аналогичные результаты для массива с несколькими сотнями ключей. Микрооптимизация, возможно, не полезна в RW, но показалась удивительной и интересной:

$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
источник
-1
$elements_array = ['first', 'second'];

функция для удаления некоторых элементов массива

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

позвонить и распечатать

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

результат Array ( [0] => first )

Абдалла Аввад Альхвалда
источник
Вопрос был о фильтрации ключей массива, а не значений.
Полетовое