I need to find a specific key in an array, and return both its value and the path to find that key. Example:
$array = array(
\'fs1\' =>
There is already an answer in wich RecursiveIteratorIterator used. My solution might not be always optimal, I have not tested estimation time. It can be optimized by redefining the callHasChildren
method of RecursiveIteratorIterator
, so there will be no children when key is found. But this is outside the domain.
Here is the approach where you do not have to use explicit inner loops:
function findKeyPathAndValue(array $array, $keyToSearch)
{
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::CHILD_FIRST
);
$path = [];
$value = null;
$depthOfTheFoundKey = null;
foreach ($iterator as $key => $current) {
if (
$key === $keyToSearch
|| $iterator->getDepth() < $depthOfTheFoundKey
) {
if (is_null($depthOfTheFoundKey)) {
$value = $current;
}
array_unshift($path, $key);
$depthOfTheFoundKey = $iterator->getDepth();
}
}
if (is_null($depthOfTheFoundKey)) {
return false;
}
return [
'path' => implode('.', $path),
'value' => $value
];
}
Pay attention to RecursiveIteratorIterator::CHILD_FIRST
. This flag reverses the order of iteration. So we can prepare our path using only one loop - this is actually the main purpose of recursive iterators. They hide all the inner loops from you.
Here is working demo.
In case you need to return array with all items matching certain key, you can use php generators
function recursiveFind(array $haystack, string $needle, $glue = '.'): ?\Generator
{
$recursive = new \RecursiveIteratorIterator(
new \RecursiveArrayIterator($haystack),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($recursive as $key => $value) {
//if the key matches our search
if ($key === $needle) {
//add the current key
$keys = [$key];
//loop up the recursive chain
for ($i = $recursive->getDepth() - 1; $i >= 0; $i--) {
array_unshift($keys, $recursive->getSubIterator($i)->key());
}
yield [
'path' => implode($glue, $keys),
'value' => $value
];
}
}
}
usage:
foreach (recursiveFind($arrayToSearch, 'keyName') as $result) {
var_dump($result);
}
Just for completion sake and future visitors. Combining the example code above and the answer I commented about to get the keys. Here is a working function that will return the requested results with one small change. In my return array I return the keys path
and value
instead of the requested 0
and $search
for the keys. I find this more verbose and easier to handle.
<?php
$array = array(
'fs1' => array(
'id1' => 0,
'foo' => 1,
'fs2' => array(
'id2' => 1,
'foo2' => 2,
'fs3' => array(
'id3' => null,
),
'fs4' => array(
'id4' => 4,
'bar' => 1,
),
),
),
);
function search($array, $searchKey=''){
//create a recursive iterator to loop over the array recursively
$iter = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST);
//loop over the iterator
foreach ($iter as $key => $value) {
//if the key matches our search
if ($key === $searchKey) {
//add the current key
$keys = array($key);
//loop up the recursive chain
for($i=$iter->getDepth()-1;$i>=0;$i--){
//add each parent key
array_unshift($keys, $iter->getSubIterator($i)->key());
}
//return our output array
return array('path'=>implode('.', $keys), 'value'=>$value);
}
}
//return false if not found
return false;
}
$searchResult1 = search($array, 'fs2');
$searchResult2 = search($array, 'fs3');
echo "<pre>";
print_r($searchResult1);
print_r($searchResult2);
outputs:
Array
(
[path] => fs1.fs2
[value] => Array
(
[id2] => 1
[foo2] => 2
[fs3] => Array
(
[id3] =>
)
[fs4] => Array
(
[id4] => 4
[bar] => 1
)
)
)
Array
(
[path] => fs1.fs2.fs3
[value] => Array
(
[id3] =>
)
)
It looks like you are assuming the keys will always be unique. I do not assume that. So, your function must return multiple values. What I would do is simply write a recursive function:
function search($array, $key, $path='')
{
foreach($array as $k=>$v)
{
if($k == $key) yield array($path==''?$k:$path.'.'.$k, array($k=>$v));
if(is_array($v))
{ // I don't know a better way to do the following...
$gen = search($v, $key, $path==''?$k:$path.'.'.$k);
foreach($gen as $v) yield($v);
}
}
}
This is a recursive generator. It returns a generator, containing all hits. It is used very much like an array:
$gen = search($array, 'fs3');
foreach($gen as $ret)
print_r($ret); // Prints out each answer from the generator