在多维数组中搜索键并返回其路径


Searching for key in multidimensional array and returning path to it

我需要在数组中找到一个特定的键,并返回其值和路径以查找该键。例:

$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, ... ))

我已经能够通过数组递归以找到正确的键并使用RecursiveArrayIterator返回数据(如下所示),但我不知道跟踪我当前所在的路径的最佳方法。

$i = new RecursiveIteratorIterator
    new RecursiveArrayIterator($array),
    RecursiveIteratorIterator::SELF_FIRST);
foreach ($i as $key => value) {
  if ($key === $search) {
    return $value;
  }
}

只是为了完成和未来的访客。结合上面的示例代码和我评论的答案来获取密钥。这是一个工作函数,它将通过一个小的更改返回请求的结果。在我的返回数组中,我返回键pathvalue,而不是请求的键0$search键。我发现这更冗长,更容易处理。

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

输出:

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

看起来您假设密钥始终是唯一的。我不这么认为。因此,函数必须返回多个值。我要做的是简单地编写一个递归函数:

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

这是一个递归生成器。它返回一个生成器,其中包含所有命中。它的使用方式非常像数组:

$gen = search($array, 'fs3');
foreach($gen as $ret)
    print_r($ret); // Prints out each answer from the generator
在使用递

归迭代器迭代器时已经有一个答案。我的解决方案可能并不总是最佳的,我没有测试估计时间。它可以通过重新定义RecursiveIteratorIteratorcallHasChildren方法进行优化,因此当找到时将没有子项。但这在域之外。

以下是您不必使用显式内部循环的方法:

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
    ];
}

注意RecursiveIteratorIterator::CHILD_FIRST。此标志颠倒了迭代顺序。因此,我们可以只使用一个循环来准备路径 - 这实际上是递归迭代器的主要目的。它们隐藏了所有的内部循环。

这是工作演示。

如果您需要返回数组,其中包含与某个键匹配的所有项目,您可以使用 php 生成器

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
            ];
        }
    }
}

用法:

foreach (recursiveFind($arrayToSearch, 'keyName') as $result) {
    var_dump($result);
}