The callback function in array_filter() only passes in the array's values, not the keys.
If I have:
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
What's the best way to delete all keys in $my_array that are not in the $allowed array?
Desired output:
$my_array = array("foo" => 1);
With array_intersect_key and array_flip:
var_dump(array_intersect_key($my_array, array_flip($allowed)));
array(1) {
["foo"]=>
int(1)
}
PHP 5.6 introduced a third parameter to array_filter(), flag, that you can set to ARRAY_FILTER_USE_KEY to filter by key instead of value:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
function ($key) use ($allowed) {
// N.b. in_array() is notorious for being slow
return in_array($key, $allowed);
},
ARRAY_FILTER_USE_KEY
);
Since PHP 7.4 introduced arrow functions we can make this more succinct:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
fn ($key) => in_array($key, $allowed),
ARRAY_FILTER_USE_KEY
);
Clearly this isn't as elegant as array_intersect_key($my_array, array_flip($allowed)), but it does offer the additional flexibility of performing an arbitrary test against the key, e.g. $allowed could contain regex patterns instead of plain strings.
You can also use ARRAY_FILTER_USE_BOTH to have both the value and the key passed to your filter function. Here's a contrived example based upon the first, but note that I'd not recommend encoding filtering rules using $allowed this way:
$my_array = ['foo' => 1, 'bar' => 'baz', 'hello' => 'wld'];
$allowed = ['foo' => true, 'bar' => true, 'hello' => 'world'];
$filtered = array_filter(
$my_array,
fn ($val, $key) => isset($allowed[$key]) && (
$allowed[$key] === true || $allowed[$key] === $val
),
ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']
Here is a more flexible solution using a closure:
$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);
Outputs:
array(1) {
'foo' =>
int(1)
}
So in the function, you can do other specific tests.
Here's a less flexible alternative using unset():
$array = array(
1 => 'one',
2 => 'two',
3 => 'three'
);
$disallowed = array(1,3);
foreach($disallowed as $key){
unset($array[$key]);
}
The result of print_r($array) being:
Array
(
[2] => two
)
This is not applicable if you want to keep the filtered values for later use but tidier, if you're certain that you don't.
If you are looking for a method to filter an array by a string occurring in keys, you can use:
$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));
The result of print_r($mResult) is
Array ( [foo] => bar [foo2] => bar2 [fooToo] => bar3 )
An adaption of this answer that supports regular expressions
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"));
Output
Array
(
[foo] => yes
[foo2] => yes
[FooToo] => yes
)
Starting from PHP 5.6, you can use the ARRAY_FILTER_USE_KEY flag in array_filter:
$result = array_filter($my_array, function ($k) use ($allowed) {
return in_array($k, $allowed);
}, ARRAY_FILTER_USE_KEY);
Otherwise, you can use this function (from 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);
});
And here is an augmented version of mine, which accepts a callback or directly the keys:
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));
Last but not least, you may also use a simple foreach:
$result = [];
foreach ($my_array as $key => $value) {
if (in_array($key, $allowed)) {
$result[$key] = $value;
}
}
How to get the current key of an array when using array_filter
Regardless of how I like Vincent's solution for Maček's problem, it doesn't actually use array_filter. If you came here from a search engine and where looking for a way to access the current iteration's key within array_filter's callback, you maybe where looking for something like this (PHP >= 5.3):
$my_array = ["foo" => 1, "hello" => "world"];
$allowed = ["foo", "bar"];
reset($my_array ); // Unnecessary in this case, as we just defined the array, but
// make sure your array is reset (see below for further explanation).
$my_array = array_filter($my_array, function($value) use (&$my_array, $allowed) {
$key = key($my_array); // request key of current internal array pointer
next($my_array); // advance internal array pointer
return isset($allowed[$key]);
});
// $my_array now equals ['foo' => 1]
It passes the array you're filtering as a reference to the callback. As array_filter doesn't conventionally iterate over the array by increasing it's public internal pointer you have to advance it by yourself.
What's important here is that you need to make sure your array is reset, otherwise you might start right in the middle of it (because the internal array pointer was left there by some code of your's that was executed before).
Based on #sepiariver I did some similar testing on PHP 8.0.3:
$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8];
$filter = ['a', 'e', 'h'];
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_intersect_key($arr, array_flip($filter));
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_intersect_key\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_filter(
$arr,
function ($key) use ($filter){return in_array($key, $filter);},
ARRAY_FILTER_USE_KEY
);
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_filter\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
foreach ($filter as $key)
if(array_key_exists($key, $arr))
$filtered[$key] = $arr[$key];
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using foreach + array_key_exists\n\n";
0.28603601455688 using array_intersect_key
1.3096671104431 using array_filter
0.19402384757996 using foreach + array_key_exists
The 'problem' of array_filter is that it will loop over all elements of $arr, whilst array_intersect_key and foreach only loop over $filter. The latter is more efficient, assuming $filter is smaller than $arr.
array filter function from php:
array_filter ( $array, $callback_function, $flag )
$array - It is the input array
$callback_function - The callback function to use, If the callback function returns true, the current value from array is returned into the result array.
$flag - It is optional parameter, it will determine what arguments are sent to callback function. If this parameter empty then callback function will take array values as argument. If you want to send array key as argument then use $flag as ARRAY_FILTER_USE_KEY. If you want to send both keys and values you should use $flag as ARRAY_FILTER_USE_BOTH .
For Example : Consider simple array
$array = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
If you want to filter array based on the array key, We need to use ARRAY_FILTER_USE_KEY as third parameter of array function array_filter.
$get_key_res = array_filter($array,"get_key",ARRAY_FILTER_USE_KEY );
If you want to filter array based on the array key and array value, We need to use ARRAY_FILTER_USE_BOTH as third parameter of array function array_filter.
$get_both = array_filter($array,"get_both",ARRAY_FILTER_USE_BOTH );
Sample Callback functions:
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;
}
}
It will output
Output of $get_key is :Array ( [a] => 1 )
Output of $get_both is :Array ( [a] => 1 )
Perhaps an overkill if you need it just once, but you can use YaLinqo library* to filter collections (and perform any other transformations). This library allows peforming SQL-like queries on objects with fluent syntax. Its where function accepts a calback with two arguments: a value and a key. For example:
$filtered = from($array)
->where(function ($v, $k) use ($allowed) {
return in_array($k, $allowed);
})
->toArray();
(The where function returns an iterator, so if you only need to iterate with foreach over the resulting sequence once, ->toArray() can be removed.)
* developed by me
Naive and ugly (but seems to be faster) solution?
Only tried this in php 7.3.11 but an ugly loop seems to execute in about a third of the time. Similar results on an array with a few hundred keys. Micro-optimization, probably not useful in RW, but found it surprising and interesting:
$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
I use a small "Utils" class where I add two filter static function to filter array using a denylist or a allowlist.
<?php
class Utils {
/**
* Filter an array based on a allowlist of keys
*
* #param array $array
* #param array $allowlist
*
* #return array
*/
public static function array_keys_allowlist( array $array, array $allowlist ): array {
return array_intersect_key( $array, array_flip( $allowlist ) );
}
/**
* Filter an array based on a denylist of keys
*
* #param array $array
* #param array $denylist
*
* #return array
*/
public static function array_keys_denylist( array $array, array $denylist ): array {
return array_diff_key($array,array_flip($denylist));
}
}
You can then use it like this
<?php
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
$my_array = Utils::array_keys_allowlist($my_array, $allowed)
Related
The callback function in array_filter() only passes in the array's values, not the keys.
If I have:
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
What's the best way to delete all keys in $my_array that are not in the $allowed array?
Desired output:
$my_array = array("foo" => 1);
With array_intersect_key and array_flip:
var_dump(array_intersect_key($my_array, array_flip($allowed)));
array(1) {
["foo"]=>
int(1)
}
PHP 5.6 introduced a third parameter to array_filter(), flag, that you can set to ARRAY_FILTER_USE_KEY to filter by key instead of value:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
function ($key) use ($allowed) {
// N.b. in_array() is notorious for being slow
return in_array($key, $allowed);
},
ARRAY_FILTER_USE_KEY
);
Since PHP 7.4 introduced arrow functions we can make this more succinct:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
fn ($key) => in_array($key, $allowed),
ARRAY_FILTER_USE_KEY
);
Clearly this isn't as elegant as array_intersect_key($my_array, array_flip($allowed)), but it does offer the additional flexibility of performing an arbitrary test against the key, e.g. $allowed could contain regex patterns instead of plain strings.
You can also use ARRAY_FILTER_USE_BOTH to have both the value and the key passed to your filter function. Here's a contrived example based upon the first, but note that I'd not recommend encoding filtering rules using $allowed this way:
$my_array = ['foo' => 1, 'bar' => 'baz', 'hello' => 'wld'];
$allowed = ['foo' => true, 'bar' => true, 'hello' => 'world'];
$filtered = array_filter(
$my_array,
fn ($val, $key) => isset($allowed[$key]) && (
$allowed[$key] === true || $allowed[$key] === $val
),
ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']
Here is a more flexible solution using a closure:
$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);
Outputs:
array(1) {
'foo' =>
int(1)
}
So in the function, you can do other specific tests.
Here's a less flexible alternative using unset():
$array = array(
1 => 'one',
2 => 'two',
3 => 'three'
);
$disallowed = array(1,3);
foreach($disallowed as $key){
unset($array[$key]);
}
The result of print_r($array) being:
Array
(
[2] => two
)
This is not applicable if you want to keep the filtered values for later use but tidier, if you're certain that you don't.
If you are looking for a method to filter an array by a string occurring in keys, you can use:
$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));
The result of print_r($mResult) is
Array ( [foo] => bar [foo2] => bar2 [fooToo] => bar3 )
An adaption of this answer that supports regular expressions
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"));
Output
Array
(
[foo] => yes
[foo2] => yes
[FooToo] => yes
)
Starting from PHP 5.6, you can use the ARRAY_FILTER_USE_KEY flag in array_filter:
$result = array_filter($my_array, function ($k) use ($allowed) {
return in_array($k, $allowed);
}, ARRAY_FILTER_USE_KEY);
Otherwise, you can use this function (from 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);
});
And here is an augmented version of mine, which accepts a callback or directly the keys:
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));
Last but not least, you may also use a simple foreach:
$result = [];
foreach ($my_array as $key => $value) {
if (in_array($key, $allowed)) {
$result[$key] = $value;
}
}
How to get the current key of an array when using array_filter
Regardless of how I like Vincent's solution for Maček's problem, it doesn't actually use array_filter. If you came here from a search engine and where looking for a way to access the current iteration's key within array_filter's callback, you maybe where looking for something like this (PHP >= 5.3):
$my_array = ["foo" => 1, "hello" => "world"];
$allowed = ["foo", "bar"];
reset($my_array ); // Unnecessary in this case, as we just defined the array, but
// make sure your array is reset (see below for further explanation).
$my_array = array_filter($my_array, function($value) use (&$my_array, $allowed) {
$key = key($my_array); // request key of current internal array pointer
next($my_array); // advance internal array pointer
return isset($allowed[$key]);
});
// $my_array now equals ['foo' => 1]
It passes the array you're filtering as a reference to the callback. As array_filter doesn't conventionally iterate over the array by increasing it's public internal pointer you have to advance it by yourself.
What's important here is that you need to make sure your array is reset, otherwise you might start right in the middle of it (because the internal array pointer was left there by some code of your's that was executed before).
Based on #sepiariver I did some similar testing on PHP 8.0.3:
$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8];
$filter = ['a', 'e', 'h'];
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_intersect_key($arr, array_flip($filter));
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_intersect_key\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_filter(
$arr,
function ($key) use ($filter){return in_array($key, $filter);},
ARRAY_FILTER_USE_KEY
);
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_filter\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
foreach ($filter as $key)
if(array_key_exists($key, $arr))
$filtered[$key] = $arr[$key];
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using foreach + array_key_exists\n\n";
0.28603601455688 using array_intersect_key
1.3096671104431 using array_filter
0.19402384757996 using foreach + array_key_exists
The 'problem' of array_filter is that it will loop over all elements of $arr, whilst array_intersect_key and foreach only loop over $filter. The latter is more efficient, assuming $filter is smaller than $arr.
array filter function from php:
array_filter ( $array, $callback_function, $flag )
$array - It is the input array
$callback_function - The callback function to use, If the callback function returns true, the current value from array is returned into the result array.
$flag - It is optional parameter, it will determine what arguments are sent to callback function. If this parameter empty then callback function will take array values as argument. If you want to send array key as argument then use $flag as ARRAY_FILTER_USE_KEY. If you want to send both keys and values you should use $flag as ARRAY_FILTER_USE_BOTH .
For Example : Consider simple array
$array = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
If you want to filter array based on the array key, We need to use ARRAY_FILTER_USE_KEY as third parameter of array function array_filter.
$get_key_res = array_filter($array,"get_key",ARRAY_FILTER_USE_KEY );
If you want to filter array based on the array key and array value, We need to use ARRAY_FILTER_USE_BOTH as third parameter of array function array_filter.
$get_both = array_filter($array,"get_both",ARRAY_FILTER_USE_BOTH );
Sample Callback functions:
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;
}
}
It will output
Output of $get_key is :Array ( [a] => 1 )
Output of $get_both is :Array ( [a] => 1 )
Perhaps an overkill if you need it just once, but you can use YaLinqo library* to filter collections (and perform any other transformations). This library allows peforming SQL-like queries on objects with fluent syntax. Its where function accepts a calback with two arguments: a value and a key. For example:
$filtered = from($array)
->where(function ($v, $k) use ($allowed) {
return in_array($k, $allowed);
})
->toArray();
(The where function returns an iterator, so if you only need to iterate with foreach over the resulting sequence once, ->toArray() can be removed.)
* developed by me
Naive and ugly (but seems to be faster) solution?
Only tried this in php 7.3.11 but an ugly loop seems to execute in about a third of the time. Similar results on an array with a few hundred keys. Micro-optimization, probably not useful in RW, but found it surprising and interesting:
$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
I use a small "Utils" class where I add two filter static function to filter array using a denylist or a allowlist.
<?php
class Utils {
/**
* Filter an array based on a allowlist of keys
*
* #param array $array
* #param array $allowlist
*
* #return array
*/
public static function array_keys_allowlist( array $array, array $allowlist ): array {
return array_intersect_key( $array, array_flip( $allowlist ) );
}
/**
* Filter an array based on a denylist of keys
*
* #param array $array
* #param array $denylist
*
* #return array
*/
public static function array_keys_denylist( array $array, array $denylist ): array {
return array_diff_key($array,array_flip($denylist));
}
}
You can then use it like this
<?php
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
$my_array = Utils::array_keys_allowlist($my_array, $allowed)
Lets say I have an array like below
$array = array(
array( //index 0
'col_1' => 'one',
'col_2' => 'two'
),
array( //index 1
'col_5' => 'five',
'col_3' => 'three'
)
);
Now I needed to search for the key value "three" and the output I want to return is 1 (array index). If there is no value exists, I want to return -1.
Actually the flow is,
Search for the value, if exists return it's index. In my scenario the index is 1
So that I can get $array[1]['col_5']
I found a solution here, but in my scenario, the column names will be differ at any time.
For ex.,
$key = array_search('three', array_column($array, '?'));
In the above code, we need to specify the column name, but in my array, the column names are different.
function search($array){
for($i=0;$i<count($array);$i++){
foreach($array[$i] as $key => $value){
if($value=='three'){
return $i;;
}
}
}
return -1;
}
Not sure what exactly you are looking for, but here is some approach:
https://ideone.com/8S6445
$keys = array_map(
function ($el) {
return array_search('three', $el) === false ? -1 : 1;
}, $array);
or probably you wanted something like:
$keys = array_map(
function ($el) {
$idx = array_search('three', $el);
return $idx === false ? -1 : $idx;
}, $array);
I hope finally I've got what is your goal:
$keys = array_map(
function ($el) {
$idx = array_search('three', $el);
if ($idx === false) {
return -1;
} else {
return array_search($idx, array_keys($el));
}
}, $array);
Even if I understood your goal, I don't think that you really need to get index 1 instead of original key col_3 - in most scenarios col_3 is what you would probably use for the following calculations, and by the way same about transformation false to -1. So my guess the function you need should be just:
$keys = array_map(
function ($el) {
return array_search('three', $el);
}, $array);
It will return real key for associative array when element is found and false if not.
Or probably I still did not get your goal?
UPDATE If you just need one value do it straight forward:
https://ideone.com/odTCxI
$key = -1;
foreach($array as $k => $el) {
if (array_search('three', $el) !== false) {
$key = $k;
break;
}
}
echo $key;
You can do this using a customized implementation of the array_filter function. However, just using a `foreach``loop would be just as effective.
Example
$array = [
[
'1' => 'one',
'2' => 'two'
],
[
'1' => 'five',
'2' => 'three',
],
];
// Set our search parameter
$needle = 'three';
$searchResult = current(array_filter(array_keys($array), function ($a) use ($array, $needle) {
return array_search($needle, $array[$a]);
})) ?: -1;
// Output
var_dump($searchResult);
I have an array such as ['id' => 1, 'name' => 'Fred'].
I want to call array_map on this array and also use the key inside the function. However, when I make a return, my keys will become int.
Simple example :
$arr = array('id' => 1, 'name' => 'Fred');
$result = array_map(
function ($value, $key) {
return $value;
},
$arr,
array_keys($arr)
);
var_dump($result);
Basically, I want $result to be identical to $arr in this case, but it turns my string keys into ints.
For your requirement of "I want to call array_map" and "$result to be identical to $arr", try:
$result = array_combine(
array_keys($arr),
array_map(function($v){ return $v; }, $arr)
);
Gives:
[
"id" => 1
"name" => "Fred"
]
But heh, if all you want is identical arrays, then nothing beats this code:
$result = $arr;
The closest you will get using array_map() is this:
<?php
$arr = array('id'=>1,'name'=>'Jon');
$callback = function ($key, $value) {
return array($key => $value);
};
$arr = array_map( $callback, array_keys($arr), $arr);
var_dump($arr);
?>
Gives:
[
[
"id" => 1
],
[
"name" => "Jon"
]
]
You will be better creating your own function with a foreach inside.
What you need is array_walk.
Try this code:
$arr = array('id' => 1, 'name' => 'Fred');
array_walk(
$arr,
function (&$value, $key) {
// do stuff
}
);
print_r($arr);
Unfortunally it works not when you try to change the keys. But you can change the values when you pass them by refference.
If you have to change the keys too, check Question to array_walk-change-keys and the first answer:
Based on #Jannie Theunissen answer the correct way of getting an array_map working with key for comparing and assigning values based on second array for example is:
$result = array_combine(
array_keys($arr),
array_map(function($v, $key){ return $v; }, $arr, array_keys($arr))
);
Or for optimized alternative:
$keys = array_keys($arr);
$result = array_combine(
$keys,
array_map(function($v, $key){ return $v; }, $arr, $keys)
);
And with a comparison array value:
$compareArray = [/*same structure as $arr but with specific values*/];
$keys = array_keys($arr);
$result = array_combine(
$keys,
array_map(function($v, $key) use ($compareArray) {
// recursive can be achieved here passing $v and $compareArray[$key]
return $compareArray[$key];
}, $arr, $keys)
);
The idea is quite simple. You need to use keys inside your array_map() function.
For this purpose we add two arrays: the first one array_keys($array) we pass the keys of our major array and the second one $array is the needed array.
array_map(function($key, $value) {
// use your key here ...
return $processed_value;
}, array_keys($array), $array)
From now on: you can use the $key as a value from first param and $value as a second param.
I'm using a map in php like so:
function func($v) {
return $v * 2;
}
$values = array(4, 6, 3);
$mapped = array_map(func, $values);
var_dump($mapped);
Is it possible to get the index of the value in the function?
Also - if I'm writing code that needs the index, should I be using a for loop instead of a map?
Sure you can, with the help of array_keys():
function func($v, $k)
{
// key is now $k
return $v * 2;
}
$values = array(4, 6, 3);
$mapped = array_map('func', $values, array_keys($values));
var_dump($mapped);
When mapping an anonymous function over an anonymous array, there is no way to access the keys:
array_map(
function($val) use ($foo) { /* ... */ },
array(key1 => val1,
key2 => val2,
/* ... */));
array_reduce doesn't get access to the keys either. array_walk can access keys, but the array is passed by reference, which requires a layer of indirection.
Some solutions are:
Array of pairs
This is bad, since we're changing the original array. Plus the boilerplate "array()" calls increase linearly with the length of the array:
array_map(
function($pair) use ($foo) {
list($key, $val) = $pair;
/* ... */
},
array(array(key1, val1),
array(key2, val2),
/* ... */));
Temporary variable
We're acting on the original array, and the boilerplate is constant, but we can easily clobber an existing variable:
$i_hope_this_does_not_conflict = array(key1 => val1,
key2 => val2,
/* ... */);
array_map(
function($key, $val) use ($foo) { /* ... */ },
array_keys($i_hope_this_does_not_conflict),
$i_hope_this_does_not_conflict);
unset($i_hope_this_does_not_conflict);
One-shot function
We can use function scope to prevent clobbering existing names, but have to add an extra layer of "use":
call_user_func(
function($arr) use ($foo) {
return array_map(function($key, $val) use ($foo) { /* ... */ },
array_keys($arr),
$arr);
},
array(key1 => val1,
key2 => val2,
/* ... */));
Multi-argument one-shot function
We define the function we're mapping in the original scope to prevent the "use" boilerplate):
call_user_func(
function($f, $arr) {
return array_map($f, array_keys($arr), $arr);
},
function($key, $val) use ($foo) { /* ... */ },
array(key1 => val1,
key2 => val2,
/* ... */));
New function
The interesting thing to note is that our last one-shot function has a nice, generic signature and looks a lot like array_map. We might want to give this a name and re-use it:
function array_mapk($f, $arr) {
return array_map($f, array_keys($arr), $arr);
}
Our application code then becomes:
array_mapk(
function($key, $val) use ($foo) { /* ... */ },
array(key1 => val1,
key2 => val2,
/* ... */));
Indirect Array Walk
When writing the above I'd ignored array_walk since it requires its argument to be passed by reference; however, I've since realised that it's easy to work around this using call_user_func. I think this is the best version so far:
call_user_func(
'array_walk',
array(key1 => val1,
key2 => val2,
/* ... */),
function($val, $key) use ($foo) { /* ... */ });
There is no way to access the index within the array_map callback. If you are working with sequential numeric indices, then an incrementing static variable could be used:
$values = ["one", "two", "three"];
$mapped = array_map(function ($value) {
static $i = 0;
$result = "Index: $i, Value: $value";
$i++;
return $result;
}, $values);
print_r($mapped);
Resulting in:
Array
(
[0] => Index: 0, Value: one
[1] => Index: 1, Value: two
[2] => Index: 2, Value: three
)
When using this approach, it's important to use an anonymous function as the callback and to never reuse that anonymous function to avoid referencing the same static variable outside of array_map.
It's a bit of an old thread but as many of you, I'm using array_keys:
array_map(function($id, $name) {
print '<option value="'.$id.'">'.$name.'</option>';
}, array_keys($array), array_values($array));
Edit: Instead of use keyword, you can add two arrays in second parameter of your arrray_map function. I think no explications needed, the code is pretty simple.
Very simple:
Only array_map fuction: does not have index key!
$params = [4,6,2,11,20];
$data = array_map(function($v) { return ":id{$v}";}, $params);
array (size=5)
0 => string ':id4' (length=4)
1 => string ':id6' (length=4)
2 => string ':id2' (length=4)
3 => string ':id11' (length=5)
4 => string ':id20' (length=5)
Now, combine with array_keys:
$data = array_map(
function($k) use ($params) { return ":id{$k}_${params[$k]}"; },
array_keys($params)
);
array (size=5)
0 => string ':id0_4' (length=6)
1 => string ':id1_6' (length=6)
2 => string ':id2_2' (length=6)
3 => string ':id3_11' (length=7)
4 => string ':id4_20' (length=7)
As mentioned by earlier posted answers, when keys and values are needed within the callback function's body, it is simplest to pass in the array of keys, then access the values for those keys via the original array.
With older versions that do not have arrow functions, use use() or global (I recommend the former) to allow access to the original array from within the callback.
With modern PHP (7.4+), use arrow function syntax. (Demo)
$values = [4, 6, 3];
var_export(
array_map(
fn($k) => "$k: " . $values[$k] * 2,
array_keys($values)
)
);
There is also no shame in using a foreach() and modifying the values by reference. (Demo)
$values = [4, 6, 3];
foreach ($values as $k => &$v) {
$v = "$k: " . $v * 2;
}
var_export($values);
You can create your own map function using foreach:
<?php
function myCallback($key, $val)
{
var_dump("myCallback - key: $key, val: $val");
return $val * 2;
}
function foreachMap($callback, $givenArray) {
$result = [];
foreach ($givenArray as $key=>$val) {
$result[$key] = $callback($key, $val);
}
return $result;
}
$values = array(4, 6, 3);
$mapped = foreachMap('myCallback', $values);
var_dump($mapped);
try: https://3v4l.org/pmFlB
For a fast and open solution (without doubling array using array_keys and similar):
/**
* Array map alternative to work with values and keys of single array.
*
* Callable receives $value and $index of $sourceArray as arguments
* If keys are not preserved via $preserveKeys - $keyCallback can be used to determinate key
*
* #param array $sourceArray
* #param callable|null $valueCallback
* #param callable|null $keyCallback
* #param bool $preserveKeys
* #return array
*/
function array_map_indexed(
array $sourceArray,
?callable $valueCallback = null,
?callable $keyCallback = null,
bool $preserveKeys = true
): array {
$newArray = [];
foreach ($sourceArray as $key => $value) {
if ($preserveKeys) {
$newArray[$keyCallback ? $keyCallback($value, $key) : $key] = $valueCallback
? $valueCallback($value, $key)
: $value;
} else {
$newArray[] = $valueCallback
? $valueCallback($value, $key)
: $value;
}
}
return $newArray;
}
Usage examples:
$result = array_map_indexed(
[
'a' => 'aValue',
'b' => 'bValue',
],
function($value, $index) {
return [$value, $index];
},
);
//Array ( [a] => Array ( [0] => aValue [1] => a ) [b] => Array ( [0] => bValue [1] => b ) )
$result = array_map_indexed(
[
'a' => 'aValue',
'b' => 'bValue',
],
function($value, $index) {
return $index.$value;
},
null,
false
);
//Array ( [0] => aaValue [1] => bbValue )
$result = array_map_indexed(
[
'a' => 'aValue',
'b' => 'bValue',
],
null,
function($value, $index) {
return $value === 'aValue' ? 'specificKey' : $index;
},
);
//Array ( [specificKey] => aValue [b] => bValue )
You can generate your own index to be used with array_map:
function func($v, $index) {
return $v * 2;
}
$values = array(4, 6, 3);
$valuesIndex = range(0, count($values) - 1);
$mapped = array_map(func, $values, $valuesIndex);
var_dump($mapped);
As shown above, you may securely create an array of numbers from 0 to the length of your array, same as a index.
Put this second array in the array_map and use it values as index in your function.
You can use the array_keys() function to get the keys of an array, and then loop through those keys using a foreach loop.
$keys = array_keys($array);
foreach($keys as $key){
// do something with $key
}
The callback function in array_filter() only passes in the array's values, not the keys.
If I have:
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
What's the best way to delete all keys in $my_array that are not in the $allowed array?
Desired output:
$my_array = array("foo" => 1);
With array_intersect_key and array_flip:
var_dump(array_intersect_key($my_array, array_flip($allowed)));
array(1) {
["foo"]=>
int(1)
}
PHP 5.6 introduced a third parameter to array_filter(), flag, that you can set to ARRAY_FILTER_USE_KEY to filter by key instead of value:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
function ($key) use ($allowed) {
// N.b. in_array() is notorious for being slow
return in_array($key, $allowed);
},
ARRAY_FILTER_USE_KEY
);
Since PHP 7.4 introduced arrow functions we can make this more succinct:
$my_array = ['foo' => 1, 'hello' => 'world'];
$allowed = ['foo', 'bar'];
$filtered = array_filter(
$my_array,
fn ($key) => in_array($key, $allowed),
ARRAY_FILTER_USE_KEY
);
Clearly this isn't as elegant as array_intersect_key($my_array, array_flip($allowed)), but it does offer the additional flexibility of performing an arbitrary test against the key, e.g. $allowed could contain regex patterns instead of plain strings.
You can also use ARRAY_FILTER_USE_BOTH to have both the value and the key passed to your filter function. Here's a contrived example based upon the first, but note that I'd not recommend encoding filtering rules using $allowed this way:
$my_array = ['foo' => 1, 'bar' => 'baz', 'hello' => 'wld'];
$allowed = ['foo' => true, 'bar' => true, 'hello' => 'world'];
$filtered = array_filter(
$my_array,
fn ($val, $key) => isset($allowed[$key]) && (
$allowed[$key] === true || $allowed[$key] === $val
),
ARRAY_FILTER_USE_BOTH
); // ['foo' => 1, 'bar' => 'baz']
Here is a more flexible solution using a closure:
$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);
Outputs:
array(1) {
'foo' =>
int(1)
}
So in the function, you can do other specific tests.
Here's a less flexible alternative using unset():
$array = array(
1 => 'one',
2 => 'two',
3 => 'three'
);
$disallowed = array(1,3);
foreach($disallowed as $key){
unset($array[$key]);
}
The result of print_r($array) being:
Array
(
[2] => two
)
This is not applicable if you want to keep the filtered values for later use but tidier, if you're certain that you don't.
If you are looking for a method to filter an array by a string occurring in keys, you can use:
$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));
The result of print_r($mResult) is
Array ( [foo] => bar [foo2] => bar2 [fooToo] => bar3 )
An adaption of this answer that supports regular expressions
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"));
Output
Array
(
[foo] => yes
[foo2] => yes
[FooToo] => yes
)
Starting from PHP 5.6, you can use the ARRAY_FILTER_USE_KEY flag in array_filter:
$result = array_filter($my_array, function ($k) use ($allowed) {
return in_array($k, $allowed);
}, ARRAY_FILTER_USE_KEY);
Otherwise, you can use this function (from 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);
});
And here is an augmented version of mine, which accepts a callback or directly the keys:
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));
Last but not least, you may also use a simple foreach:
$result = [];
foreach ($my_array as $key => $value) {
if (in_array($key, $allowed)) {
$result[$key] = $value;
}
}
How to get the current key of an array when using array_filter
Regardless of how I like Vincent's solution for Maček's problem, it doesn't actually use array_filter. If you came here from a search engine and where looking for a way to access the current iteration's key within array_filter's callback, you maybe where looking for something like this (PHP >= 5.3):
$my_array = ["foo" => 1, "hello" => "world"];
$allowed = ["foo", "bar"];
reset($my_array ); // Unnecessary in this case, as we just defined the array, but
// make sure your array is reset (see below for further explanation).
$my_array = array_filter($my_array, function($value) use (&$my_array, $allowed) {
$key = key($my_array); // request key of current internal array pointer
next($my_array); // advance internal array pointer
return isset($allowed[$key]);
});
// $my_array now equals ['foo' => 1]
It passes the array you're filtering as a reference to the callback. As array_filter doesn't conventionally iterate over the array by increasing it's public internal pointer you have to advance it by yourself.
What's important here is that you need to make sure your array is reset, otherwise you might start right in the middle of it (because the internal array pointer was left there by some code of your's that was executed before).
Based on #sepiariver I did some similar testing on PHP 8.0.3:
$arr = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8];
$filter = ['a', 'e', 'h'];
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_intersect_key($arr, array_flip($filter));
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_intersect_key\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
$filtered = array_filter(
$arr,
function ($key) use ($filter){return in_array($key, $filter);},
ARRAY_FILTER_USE_KEY
);
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using array_filter\n\n";
$filtered = [];
$time = microtime(true);
$i = 1000000;
while($i) {
foreach ($filter as $key)
if(array_key_exists($key, $arr))
$filtered[$key] = $arr[$key];
$i--;
}
print_r($filtered);
echo microtime(true) - $time . " using foreach + array_key_exists\n\n";
0.28603601455688 using array_intersect_key
1.3096671104431 using array_filter
0.19402384757996 using foreach + array_key_exists
The 'problem' of array_filter is that it will loop over all elements of $arr, whilst array_intersect_key and foreach only loop over $filter. The latter is more efficient, assuming $filter is smaller than $arr.
array filter function from php:
array_filter ( $array, $callback_function, $flag )
$array - It is the input array
$callback_function - The callback function to use, If the callback function returns true, the current value from array is returned into the result array.
$flag - It is optional parameter, it will determine what arguments are sent to callback function. If this parameter empty then callback function will take array values as argument. If you want to send array key as argument then use $flag as ARRAY_FILTER_USE_KEY. If you want to send both keys and values you should use $flag as ARRAY_FILTER_USE_BOTH .
For Example : Consider simple array
$array = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
If you want to filter array based on the array key, We need to use ARRAY_FILTER_USE_KEY as third parameter of array function array_filter.
$get_key_res = array_filter($array,"get_key",ARRAY_FILTER_USE_KEY );
If you want to filter array based on the array key and array value, We need to use ARRAY_FILTER_USE_BOTH as third parameter of array function array_filter.
$get_both = array_filter($array,"get_both",ARRAY_FILTER_USE_BOTH );
Sample Callback functions:
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;
}
}
It will output
Output of $get_key is :Array ( [a] => 1 )
Output of $get_both is :Array ( [a] => 1 )
Perhaps an overkill if you need it just once, but you can use YaLinqo library* to filter collections (and perform any other transformations). This library allows peforming SQL-like queries on objects with fluent syntax. Its where function accepts a calback with two arguments: a value and a key. For example:
$filtered = from($array)
->where(function ($v, $k) use ($allowed) {
return in_array($k, $allowed);
})
->toArray();
(The where function returns an iterator, so if you only need to iterate with foreach over the resulting sequence once, ->toArray() can be removed.)
* developed by me
Naive and ugly (but seems to be faster) solution?
Only tried this in php 7.3.11 but an ugly loop seems to execute in about a third of the time. Similar results on an array with a few hundred keys. Micro-optimization, probably not useful in RW, but found it surprising and interesting:
$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
I use a small "Utils" class where I add two filter static function to filter array using a denylist or a allowlist.
<?php
class Utils {
/**
* Filter an array based on a allowlist of keys
*
* #param array $array
* #param array $allowlist
*
* #return array
*/
public static function array_keys_allowlist( array $array, array $allowlist ): array {
return array_intersect_key( $array, array_flip( $allowlist ) );
}
/**
* Filter an array based on a denylist of keys
*
* #param array $array
* #param array $denylist
*
* #return array
*/
public static function array_keys_denylist( array $array, array $denylist ): array {
return array_diff_key($array,array_flip($denylist));
}
}
You can then use it like this
<?php
$my_array = array("foo" => 1, "hello" => "world");
$allowed = array("foo", "bar");
$my_array = Utils::array_keys_allowlist($my_array, $allowed)