Поиск ключа в многомерном массиве и возврат пути к нему

Мне нужно найти конкретный ключ в массиве и вернуть и его значение, и путь, чтобы найти этот ключ. Пример:

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

3

Решение

Просто ради завершения и будущие посетители. Комбинируя приведенный выше пример кода и ответ, который я прокомментировал, чтобы получить ключи. Вот рабочая функция, которая выдаст запрошенные результаты с одним небольшим изменением. В моем массиве возврата я возвращаю ключи path а также value вместо запрошенного 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] =>
)

)
3

Другие решения

Похоже, вы предполагаете, что ключи всегда будут уникальными. Я не предполагаю этого. Итак, ваша функция должна возвращать несколько значений. Я бы просто написал рекурсивную функцию:

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
1

Уже есть ответ в котором RecursiveIteratorIterator используемый. Мое решение может быть не всегда оптимальным, я не проверял время оценки. Это может быть оптимизировано путем переопределения callHasChildren метод RecursiveIteratorIteratorтак что не будет детей, когда ключ найден. Но это за пределами домена.

Вот подход, где вам не нужно использовать явные внутренние циклы:

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, Этот флаг меняет порядок итераций. Таким образом, мы можем подготовить наш путь, используя только один цикл — это фактически основная цель рекурсивных итераторов. Они скрывают все внутренние петли от вас.

Вот рабочая демо.

1

Если вам нужно вернуть массив со всеми элементами, соответствующими определенному ключу, вы можете использовать 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);
}
0
По вопросам рекламы [email protected]