可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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' => array(
'id1' => 0,
'foo' => 1,
'fs2' => array(
'id2' => 1,
'foo2' => 2,
'fs3' => array(
'id3' => null,
),
'fs4' => array(
'id4' => 4,
'bar' => 1,
),
),
),
);
search($array, 'fs3'); // Returns ('fs1.fs2.fs3', array('id3' => null))
search($array, 'fs2'); // Returns ('fs1.fs2', array('id2' => 1, ... ))
I've been able to recurse through the array to find the correct key and return the data using RecursiveArrayIterator
(shown below), but I don't know the best way to keep track of what path I'm currently on.
$i = new RecursiveIteratorIterator
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST);
foreach ($i as $key => value) {
if ($key === $search) {
return $value;
}
}
回答1:
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] =>
)
)
回答2:
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
回答3:
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.
回答4:
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);
}