PHP: Як використовувати array_filter () для фільтрації ключів масиву?


363

Функція зворотного дзвінка array_filter()передає лише значення масиву, а не клавіші.

Якщо я маю:

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

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

Який найкращий спосіб видалити всі ключі $my_array, які не знаходяться в $allowedмасиві?

Бажаний вихід:

$my_array = array("foo" => 1);

Не вирішення , але інший підхід , який може бути корисний для $b = ['foo' => $a['foo'], 'bar' => $a['bar']]Це призведе до $b['bar']В null.
oriadam

Відповіді:


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, які присутні у дозволеному $.
Вінсент Савард

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
Я б точно не назвав це "більш гнучким"; він також відчуває себе набагато менш простим, ніж прийняте рішення.
maček

Я згоден. Було б гнучкіше, якщо б умова була більш складною.
Згорнутий

1
Тільки проходячи повз, для інших користувачів: Це рішення не стосується випадку, коли $ my_array має дублікати значень або значень, які не є цілими чи рядками. Тому я б не використовував це рішення.
користувач23127

2
Я згоден, це більш гнучко, оскільки дозволяє змінювати логіку фільтра. Наприклад, я використав масив недозволених ключів і просто повернувся! In_array ($ ключ, $ заборонено).
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 у масиві $, перш ніж виконувати скидання.
Ярек Якубовський

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 )

$ масив - це вхідний масив

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

}

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

Вихід із запуску


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

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


Питання стосувалося фільтрації ключів масивів, а не значень.
poletaew
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.