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)
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)
I want to search through a 2-dimensional array, but I only want to search in a specific field in the 2nd Dimension. If found, I would like to return the Key. No need to go on from there, I only need the first occurence but I wouldn't mind to get all occurences either.
The Array might look like this:
$array = [
0 => ['value' => 'x', 'foo' => 'bar'],
1 => ['value' => 'y', 'foo' => 'bar'],
2 => ['value' => 'z', 'foo' => 'x'],
];
Now my first thought would be something like this:
function myCustomArraySearch($array, $searchkey, $searchvalue) {
foreach ($array as $key => $value) {
if ($value[$searchkey] == $searchvalue) {
return $key;
}
}
return false;
}
echo myCustomArraySearch($array, 'value', 'x');
I'm sure, there is a more elegnt solution. Any ideas?
Here is one way that returns a single key:
$result = array_search('x', array_column($array, 'value'));
This will return multiple keys:
$result = array_keys(array_column($array, 'value'), 'x');
If you don't have PHP >= 5.5.0 needed for array_column() then use this in it's place:
array_map(function($v) { return $v['value']; }, $array)
The functions below returns the position of the first occurrence:
1 - Using foreach iteration and array_serach
function search1($array, $key, $value) {
foreach ($array as $k => $arr) {
if (array_search($value, $arr) != false) {
return $k;
}
}
return false;
}
2 - Using array_map, array_key_exists and array_search.
function search2($array, $key, $value) {
$mapped = array_map(function($arr) use ($key, $value) {
return (array_key_exists($key, $arr) && $arr[$key] == $value)
? true
: false;
},
$array);
return array_search(true, $mapped);
}
Your code is working fine so this code just does it in less lines. Only works for PHP 5.5+.
function myCustomArraySearch($array, $searchkey, $searchvalue) {
$cols = array_column($array, $searchkey);
$result = array_search($searchvalue, $cols);
return $result;
}
Of course, if you wanted to return the array it found and not just the index you would just return like so:
function myCustomArraySearch($array, $searchkey, $searchvalue) {
$cols = array_column($array, $searchkey);
$result = array_search($searchvalue, $cols);
return $array[$result];
}
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)
Example:
$arr = array(
'apple' => 'sweet',
'grapefruit' => 'bitter',
'pear' => 'tasty',
'banana' => 'yellow'
);
I want to switch the positions of grapefruit and pear, so the array will become
$arr = array(
'apple' => 'sweet',
'pear' => 'tasty',
'grapefruit' => 'bitter',
'banana' => 'yellow'
)
I know the keys and values of the elements I want to switch, is there an easy way to do this? Or will it require a loop + creating a new array?
Thanks
Just a little shorter and less complicated than the solution of arcaneerudite:
<?php
if(!function_exists('array_swap_assoc')) {
function array_swap_assoc($key1, $key2, $array) {
$newArray = array ();
foreach ($array as $key => $value) {
if ($key == $key1) {
$newArray[$key2] = $array[$key2];
} elseif ($key == $key2) {
$newArray[$key1] = $array[$key1];
} else {
$newArray[$key] = $value;
}
}
return $newArray;
}
}
$array = $arrOrig = array(
'fruit' => 'pear',
'veg' => 'cucumber',
'tuber' => 'potato',
'meat' => 'ham'
);
$newArray = array_swap_assoc('veg', 'tuber', $array);
var_dump($array, $newArray);
?>
Tested and works fine
Here's my version of the swap function:
function array_swap_assoc(&$array,$k1,$k2) {
if($k1 === $k2) return; // Nothing to do
$keys = array_keys($array);
$p1 = array_search($k1, $keys);
if($p1 === FALSE) return; // Sanity check...keys must exist
$p2 = array_search($k2, $keys);
if($p2 === FALSE) return;
$keys[$p1] = $k2; // Swap the keys
$keys[$p2] = $k1;
$values = array_values($array);
// Swap the values
list($values[$p1],$values[$p2]) = array($values[$p2],$values[$p1]);
$array = array_combine($keys, $values);
}
if the array comes from the db, add a sort_order field so you can always be sure in what order the elements are in the array.
This may or may not be an option depending on your particular use-case, but if you initialize your array with null values with the appropriate keys before populating it with data, you can set the values in any order and the original key-order will be maintained. So instead of swapping elements, you can prevent the need to swap them entirely:
$arr = array('apple' => null,
'pear' => null,
'grapefruit' => null,
'banana' => null);
...
$arr['apple'] = 'sweet';
$arr['grapefruit'] = 'bitter'; // set grapefruit before setting pear
$arr['pear'] = 'tasty';
$arr['banana'] = 'yellow';
print_r($arr);
>>> Array
(
[apple] => sweet
[pear] => tasty
[grapefruit] => bitter
[banana] => yellow
)
Not entirely sure if this was mentioned, but, the reason this is tricky is because it's non-indexed.
Let's take:
$arrOrig = array(
'fruit'=>'pear',
'veg'=>'cucumber',
'tuber'=>'potato'
);
Get the keys:
$arrKeys = array_keys($arrOrig);
print_r($arrKeys);
Array(
[0]=>fruit
[1]=>veg
[2]=>tuber
)
Get the values:
$arrVals = array_values($arrOrig);
print_r($arrVals);
Array(
[0]=>pear
[1]=>cucumber
[2]=>potato
)
Now you've got 2 arrays that are numerical. Swap the indices of the ones you want to swap, then read the other array back in in the order of the modified numerical array. Let's say we want to swap 'fruit' and 'veg':
$arrKeysFlipped = array_flip($arrKeys);
print_r($arrKeysFlipped);
Array (
[fruit]=>0
[veg]=>1
[tuber]=>2
)
$indexFruit = $arrKeysFlipped['fruit'];
$indexVeg = $arrKeysFlipped['veg'];
$arrKeysFlipped['veg'] = $indexFruit;
$arrKeysFlipped['fruit'] = $indexVeg;
print_r($arrKeysFlipped);
Array (
[fruit]=>1
[veg]=>0
[tuber]=>2
)
Now, you can swap back the array:
$arrKeys = array_flip($arrKeysFlipped);
print_r($arrKeys);
Array (
[0]=>veg
[1]=>fruit
[2]=>tuber
)
Now, you can build an array by going through the oringal array in the 'order' of the rearranged keys.
$arrNew = array ();
foreach($arrKeys as $index=>$key) {
$arrNew[$key] = $arrOrig[$key];
}
print_r($arrNew);
Array (
[veg]=>cucumber
[fruit]=>pear
[tuber]=>potato
)
I haven't tested this - but this is what I'd expect. Does this at least provide any kind of help? Good luck :)
You could put this into a function $arrNew = array_swap_assoc($key1,$key2,$arrOld);
<?php
if(!function_exists('array_swap_assoc')) {
function array_swap_assoc($key1='',$key2='',$arrOld=array()) {
$arrNew = array ();
if(is_array($arrOld) && count($arrOld) > 0) {
$arrKeys = array_keys($arrOld);
$arrFlip = array_flip($arrKeys);
$indexA = $arrFlip[$key1];
$indexB = $arrFlip[$key2];
$arrFlip[$key1]=$indexB;
$arrFlip[$key2]=$indexA;
$arrKeys = array_flip($arrFlip);
foreach($arrKeys as $index=>$key) {
$arrNew[$key] = $arrOld[$key];
}
} else {
$arrNew = $arrOld;
}
return $arrNew;
}
}
?>
WARNING: Please test and debug this before just using it - no testing has been done at all.
There is no easy way, just a loop or a new array definition.
Classical associative array doesn't define or guarantee sequence of elements in any way. There is plain array/vector for that. If you use associative array you are assumed to need random access but not sequential. For me you are using assoc array for task it is not made for.
yeah I agree with Lex, if you are using an associative array to hold data, why not using your logic handle how they are accessed instead of depending on how they are arranged in the array.
If you really wanted to make sure they were in a correct order, trying creating fruit objects and then put them in a normal array.
There is no easy way to do this. This sounds like a slight design-logic error on your part which has lead you to try to do this when there is a better way to do whatever it is you are wanting to do. Can you tell us why you want to do this?
You say that I know the keys and values of the elements I want to switch which makes me think that what you really want is a sorting function since you can easily access the proper elements anytime you want as they are.
$value = $array[$key];
If that is the case then I would use sort(), ksort() or one of the many other sorting functions to get the array how you want. You can even use usort() to Sort an array by values using a user-defined comparison function.
Other than that you can use array_replace() if you ever need to swap values or keys.
Here are two solutions. The first is longer, but doesn't create a temporary array, so it saves memory. The second probably runs faster, but uses more memory:
function swap1(array &$a, $key1, $key2)
{
if (!array_key_exists($key1, $a) || !array_key_exists($key2, $a) || $key1 == $key2) return false;
$after = array();
while (list($key, $val) = each($a))
{
if ($key1 == $key)
{
break;
}
else if ($key2 == $key)
{
$tmp = $key1;
$key1 = $key2;
$key2 = $tmp;
break;
}
}
$val1 = $a[$key1];
$val2 = $a[$key2];
while (list($key, $val) = each($a))
{
if ($key == $key2)
$after[$key1] = $val1;
else
$after[$key] = $val;
unset($a[$key]);
}
unset($a[$key1]);
$a[$key2] = $val2;
while (list($key, $val) = each($after))
{
$a[$key] = $val;
unset($after[$key]);
}
return true;
}
function swap2(array &$a, $key1, $key2)
{
if (!array_key_exists($key1, $a) || !array_key_exists($key2, $a) || $key1 == $key2) return false;
$swapped = array();
foreach ($a as $key => $val)
{
if ($key == $key1)
$swapped[$key2] = $a[$key2];
else if ($key == $key2)
$swapped[$key1] = $a[$key1];
else
$swapped[$key] = $val;
}
$a = $swapped;
return true;
}
fwiw here is a function to swap two adjacent items to implement moveUp() or moveDown() in an associative array without foreach()
/**
* #param array $array to modify
* #param string $key key to move
* #param int $direction +1 for down | -1 for up
* #return $array
*/
protected function moveInArray($array, $key, $direction = 1)
{
if (empty($array)) {
return $array;
}
$keys = array_keys($array);
$index = array_search($key, $keys);
if ($index === false) {
return $array; // not found
}
if ($direction < 0) {
$index--;
}
if ($index < 0 || $index >= count($array) - 1) {
return $array; // at the edge: cannot move
}
$a = $keys[$index];
$b = $keys[$index + 1];
$result = array_slice($array, 0, $index, true);
$result[$b] = $array[$b];
$result[$a] = $array[$a];
return array_merge($result, array_slice($array, $index + 2, null, true));
}
There is an easy way:
$sourceArray = array(
'apple' => 'sweet',
'grapefruit' => 'bitter',
'pear' => 'tasty',
'banana' => 'yellow'
);
// set new order
$orderArray = array(
'apple' => '', //this values would be replaced
'pear' => '',
'grapefruit' => '',
//it is not necessary to touch all elemets that will remains the same
);
$result = array_replace($orderArray, $sourceArray);
print_r($result);
and you get:
$result = array(
'apple' => 'sweet',
'pear' => 'tasty',
'grapefruit' => 'bitter',
'banana' => 'yellow'
)
function arr_swap_keys(array &$arr, $key1, $key2, $f_swap_vals=false) {
// if f_swap_vals is false, then
// swap only the keys, keeping the original values in their original place
// ( i.e. do not preserve the key value correspondence )
// i.e. if arr is (originally)
// [ 'dog' => 'alpha', 'cat' => 'beta', 'horse' => 'gamma' ]
// then calling this on arr with, e.g. key1 = 'cat', and key2 = 'horse'
// will result in arr becoming:
// [ 'dog' => 'alpha', 'horse' => 'beta', 'cat' => 'gamma' ]
//
// if f_swap_vals is true, then preserve the key value correspondence
// i.e. in the above example, arr will become:
// [ 'dog' => 'alpha', 'horse' => 'gamma', 'cat' => 'beta' ]
//
//
$arr_vals = array_values($arr); // is a (numerical) index to value mapping
$arr_keys = array_keys($arr); // is a (numerical) index to key mapping
$arr_key2idx = array_flip($arr_keys);
$idx1 = $arr_key2idx[$key1];
$idx2 = $arr_key2idx[$key2];
swap($arr_keys[$idx1], $arr_keys[$idx2]);
if ( $f_swap_vals ) {
swap($arr_vals[$idx1], $arr_vals[$idx2]);
}
$arr = array_combine($arr_keys, $arr_vals);
}
function swap(&$a, &$b) {
$t = $a;
$a = $b;
$b = $t;
}
Well it's just a key sorting problem. We can use uksort for this purpose. It needs a key comparison function and we only need to know that it should return 0 to leave keys position untouched and something other than 0 to move key up or down.
Notice that it will only work if your keys you want to swap are next to each other.
<?php
$arr = array(
'apple' => 'sweet',
'grapefruit' => 'bitter',
'pear' => 'tasty',
'banana' => 'yellow'
);
uksort(
$arr,
function ($k1, $k2) {
if ($k1 == 'grapefruit' && $k2 == 'pear') return 1;
else return 0;
}
);
var_dump($arr);
I'll share my short version too, it works with both numeric and associative arrays.
array array_swap ( array $array , mixed $key1 , mixed $key2 [, bool $preserve_keys = FALSE [, bool $strict = FALSE ]] )
Returns a new array with the two elements swapped. It preserve original keys if specified. Return FALSE if keys are not found.
function array_swap(array $array, $key1, $key2, $preserve_keys = false, $strict = false) {
$keys = array_keys($array);
if(!array_key_exists($key1, $array) || !array_key_exists($key2, $array)) return false;
if(($index1 = array_search($key1, $keys, $strict)) === false) return false;
if(($index2 = array_search($key2, $keys, $strict)) === false) return false;
if(!$preserve_keys) list($keys[$index1], $keys[$index2]) = array($key2, $key1);
list($array[$key1], $array[$key2]) = array($array[$key2], $array[$key1]);
return array_combine($keys, array_values($array));
}
For example:
$arr = array_swap($arr, 'grapefruit', 'pear');
I wrote a function with more general purpose, with this problem in mind.
array with known keys
specify order of keys in a second array ($order array keys indicate key position)
function order_array($array, $order) {
foreach (array_keys($array) as $k => $v) {
$keys[++$k] = $v;
}
for ($i = 1; $i <= count($array); $i++) {
if (isset($order[$i])) {
unset($keys[array_search($order[$i], $keys)]);
}
if ($i === count($array)) {
array_push($keys, $order[$i]);
} else {
array_splice($keys, $i-1, 0, $order[$i]);
}
}
}
foreach ($keys as $key) {
$result[$key] = $array[$key];
}
return $result;
} else {
return false;
}
}
$order = array(1 => 'item3', 2 => 'item5');
$array = array("item1" => 'val1', "item2" => 'val2', "item3" => 'val3', "item4" => 'val4', "item5" => 'val5');
print_r($array); -> Array ( [item1] => val1 [item2] => val2 [item3] => val3 [item4] => val4 [item5] => val5 )
print_r(order_array($array, $order)); -> Array ( [item3] => val3 [item5] => val5 [item1] => val1 [item2] => val2 [item4] => val4 )
I hope this is relevant / helpful for someone
Arrays in php are ordered maps.
$arr = array('apple'=>'sweet','grapefruit'=>'bitter','
pear'=>'tasty','banana'=>'yellow');
doesn't mean that that the first element is 'apple'=>'sweet' and the last - 'banana'=>'yellow' just because you put 'apple' first and 'banana' last. Actually, 'apple'=>'sweet' will be the first and
'banana'=>'yellow' will be the second because of alphabetical ascending sort order.