递归遍历数组并打印步行路径

14

有人可以帮我提供一些递归遍历数组并在到达最后一个元素时打印完整路径的代码或指令吗?一个简单的echo命令就可以了,因为我会将该代码适应到我正在开发的其他函数中。

该函数不需要确定数组的维度,因为这个参数将被传递:

示例:

$depth = 8;

$array[1][3][5][6][9][5][8][9];

当函数达到第8个元素时,它会打印出所有通往该元素的路径:

//print path
'1 -> 3 -> 5 -> 6 -> 9 -> 5 -> 8 -> 9'
  • 正如我所说的,只有以这种格式打印才能起作用,因为我将把代码实现到其他一些函数中。

  • 数组键可以具有相同的值。显然不是整个数组中相同顺序的相同值。

更新:

递归遍历函数:

$someArray[1][2][3] = 'end';
$someArray[1][2][6] = 'end';
$someArray[1][3][6] = 'end';
$someArray[4][3][7] = 'end';

function listArrayRecursive(&$array_name, $ident = 0){
    if (is_array($array_name)){
        foreach ($array_name as $k => &$v){
            if (is_array($v)){
                for ($i=0; $i < $ident * 10; $i++){ echo "&nbsp;"; }
                echo $k . " : " . "<br>";
                listArrayRecursive($v, $ident + 1);
            }else{
                for ($i=0; $i < $ident * 10; $i++){ echo "&nbsp;"; }
                echo $k . " : " . $v . "<br>";
            }
        }
    }else{
        echo "Variable = " . $array_name;
    }
}

listArrayRecursive($someArray);

会打印出:

1 :
      2 :
                3 : end
                6 : end
      3 :
                6 : end
4 :
      3 :
                7 : end

现在,我如何在每次到达数组结尾时也打印出该数组的路径呢?例如:

1 :
      2 :
                3 : end : path -> 1,2,3
                6 : end : path -> 1,2,6
      3 :
                6 : end : path -> 1,3,6
4 :
      3 :
                7 : end : path -> 4,3,7

编辑过的代码添加了第三个参数以记录路径:

$someArray[1][2][3] = 'end';
$someArray[1][2][6] = 'end';
$someArray[1][3][6] = 'end';
$someArray[4][3][7] = 'end';
$someArray[3][2] = 'end';

function listArrayRecursive(&$array_name, $ident = 0, $path = null){
     foreach ($array_name as $k => &$v){
         if (is_array($v)){
            for ($i=0; $i < $ident * 10; $i++){ echo "&nbsp;"; }
            echo $k . " : " . "<br>";
            $path .= $k . ', ';
            listArrayRecursive($v, $ident + 1, $path);
        }else{
             for ($i=0; $i < $ident * 10; $i++){ echo "&nbsp;"; }
             echo $k . " : " . $v . ' - path -> ' . $path . "<br>";
        }
    }
}

listArrayRecursive($someArray);

将打印:

1 :
          2 :
                    3 : end - path -> 1, 2,
                    6 : end - path -> 1, 2,
          3 :
                    6 : end - path -> 1, 2, 3,
4 :
          3 :
                    7 : end - path -> 1, 4, 3,
3 :
          2 : end - path -> 1, 4, 3, 

1
http://codepad.viper-7.com/tW52eW - NullUserException
你之前发过这个问题吗?那段代码看起来很熟悉。 - Herbert
@salathe 我发布了代码和执行结果:http://codepad.org/iyrcdfQP ... 它将用于打印菜单和执行其他任务。我正在设置一个地图数组,以便为我动态生成菜单、表格和表单。在 ['map'] 键内,递归应该只遍历数字,这就是为什么我使用 is_number 的原因。所有其他元素都将成为数组所在级别的设置。深度将是数组的维数。它将用于打印带有多个维度的下拉菜单。如上面的评论所述,这些应该有最多 3 个维度... - Henrique
@Henrique 不是的,这只是相同的代码,但我正在使用viper7,因为它将输出显示为HTML而不是纯文本。 - NullUserException
我明白了。也许在这个问题被标记为完全重复之前,你应该投票删除另一个问题。 - Herbert
显示剩余8条评论
9个回答

23

您可以使用RecursiveIteratorIterator (文档)来遍历数组,省去递归时的繁琐工作。

function listArrayRecursive($someArray) {
    $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($someArray), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($iterator as $k => $v) {
        $indent = str_repeat('&nbsp;', 10 * $iterator->getDepth());
        // Not at end: show key only
        if ($iterator->hasChildren()) {
            echo "$indent$k :<br>";
        // At end: show key, value and path
        } else {
            for ($p = array(), $i = 0, $z = $iterator->getDepth(); $i <= $z; $i++) {
                $p[] = $iterator->getSubIterator($i)->key();
            }
            $path = implode(',', $p);
            echo "$indent$k : $v : path -> $path<br>";
        }
    }
}

这是一个很好的解决方案,但你必须小心,因为如果你的叶子是一个对象,“$iterator->hasChildren()”会返回true,代码实际上不会打印导致叶子对象的路径。我在我的答案中找到了一个解决方法。 - tonix
终于找到了我问题的答案!但我还缺少最后一件事:如何将数组索引打印在方括号而不是逗号中?geometry,5,obs,0,latitude 应该是 geometry[5]->obs[0]->latitude。 - jumpjack

0
$a= array(1,2,3,4,5,6);
$val = end($a);
print_array($a,$val);
function print_array(&$arr, $val)
{
    if ($val === false)
        return;

    $curr = prev($arr);
    print_array($arr,$curr);
    echo $val;
}

0
我根据@salathe的函数编写了以下函数。它返回一个数组,其中每个元素都是一个包含索引0处叶子和索引1处路径键数组的数组:
function loosenMultiDimensionalArrayPathForEachVal($array) {
    $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::SELF_FIRST);        
    $iterator->rewind();
    $res = [];
    foreach ($iterator as $v) {
        $depth = $iterator->getDepth();
        for ($path = array(), $i = 0, $z = $depth; $i <= $z; $i++) {
            $path[] = $iterator->getSubIterator($i)->key();
        }
        $leaf = $array;
        foreach ($path as $pathKey) {
            $leaf = $leaf[$pathKey];
        }
        if (!is_array($leaf)) {
            $res[] = [
                $v,
                $path
            ];
        }
    }
    return $res;
}

我实现这个的主要原因是,如果当前迭代的叶子是一个对象,$iterator->hasChildren()会返回true。因此,我不能通过这种方式获取它的路径。

致命错误:无法在xxxx行中将stdClass对象用作数组。 - jumpjack
你能否使用这个函数发布你的代码,以便我可以尝试复制它? - tonix
$file_contents = file_get_contents("data.json");$json_dump = json_decode($file_contents);listArrayRecursive($json_dump);loosenMultiDimensionalArrayPathForEachVal($json_dump);
第一个调用正常工作,第二个调用引发错误。
- jumpjack
感谢提供示例。您可以通过将json_decode的第二个参数bool $assoc(默认为false)设置为true来获得预期输出,即:json_decode($file_contents, true);loosenMultiDimensionalArrayPathForEachVal假定您正在传递一个多维数组,而不是一个stdClass对象。如果有帮助,请给我的答案点赞。 - tonix
以这种方式,它只是不打印任何内容(但listArrayRecursive()仍在工作)。它被执行了,因为如果我在这里和那里放置ECHO“debug”,它会打印“debug”...但这就是它所做的。 - jumpjack
显示剩余2条评论

0
我有类似的问题。这里是一个深度优先搜索的解决方案(不包括路径深度,它会一直到数组的最后)。如果您不想包含该值,请注释掉“if”语句:
$output = array();
retrievePath($someArray, $output);

function retrievePath($someArray, array &$pathKeeper)
{
    if(!is_array($someArray)){ // $someArray == "end"
        $element = array_pop($pathKeeper) ?? '';// if the array is empty pop returns null, we don't want that
        array_push($pathKeeper, $element . '->'. $someArray);
    } else{
        end($someArray);//we want to get the last element from the array so we move the internal pointer to it's end
        $endElKey = key($someArray);//take the key where the pointer is
        reset($someArray);
        foreach($someArray as $key=>$value){
            $element = array_pop($pathKeeper);
            array_push($pathKeeper, $element === null ? $key : $element . '->' . $key);// we don't want '->' at the beginning
            retrievePath($value, $pathKeeper);
            if($key != $endElKey) //we check whether this is not the last loop
                array_push($pathKeeper, $element);
        }
    }
}

0
<?php
function printListRecursive($a, $var='', $i = 0) {
    if (!is_array($a)) {
        $var .= $a;
        return $var;
    }
    $string = "";
    foreach ($a as $k => $value) {
        $string .= str_repeat("&nbsp;&nbsp;", $i) .' - '. $k . ':';
        if (!is_array($value)) {
            $string .= $value . '<br />';
        } else {
            $string .= '<br />';
            $string .= printListRecursive($value, $var, $i + 1);
        }
    }
    return $string;
}
$test_array = [
    'America' => [
        'Argentina' => 'Buenos Aires',
        'Peru' => 'Lima'
    ],
    'Europe' => [
        'Ireland' => 'Dublin',
        'France' => 'Paris',
        'Italy' => 'Rome'
    ]
];
$result = printListRecursive($test_array);
echo $result;
?>

在此处检查代码


沙盒演示不允许运行。请编辑您的答案,提供一个当前可用的沙盒。 - mickmackusa

0
我刚刚写了一个函数,使递归循环变得更容易: 类似于array_walk_recursive,但具有一些额外的功能。
public static function walk($array, $callback, $custom = null, $recursive = false, $info = [])
{
    $r = $recursive;
    if (gettype($r) === 'integer') {
        $r--;
    }
    $info['depth'] = empty($info)?1:$info['depth'] + 1;
    $info['count'] = count($array);
    $info['i'] = 1;
    foreach($array as $k => $v) {
        if (is_array($v) && $r > 0) {
            $array[$k] = static::walk($v, $callback, $custom, $r, $info);
        } else {
            $array[$k] = $callback($v, $k, $custom, $info);
        }
        $info['i'] ++;
    }
    return $array;
}

public static function walkable($v, $k, $custom, $info)
{
    if (is_string($v)) {
        return $v." [ custom: {$custom['key']} ] [ level: ".$info['depth'].' | No '.$info['i'].' of '.$info['count']." ]";
    }
    return $v;
}

这样调用:

   $result = Namespace\ClassName::walk($array, ['Namespace\ClassName', 'walkable'], ['key'=>'value'], true);

将 recursive 设置为 false 将只评估第一层。

将 recursive 设置为 true 将导致其遍历整个数组。

将 recursive 设置为整数将导致其仅遍历到该深度。

可行走函数可以被引用或作为匿名函数传递给回调函数。

(期望:value、key、custom、info)返回的值将替换当前值。

可以传递自定义数据,并为您提供一些附加信息。

如果需要额外的信息,可以扩展 walk 函数。


0

这个例子是为了给你一个想法,而不是解决实际任务。

function recursiveSearch($array,$search){
    foreach($array as $key=>$val){
        if($val==$search)return $key;
        $x=recursiveSearch($array[$key],$search);
        if($x)return $key.' -> '.$x;
    }
}

echo recursiveSearch($array,'search');

如果没有找到匹配项,则返回 null。


0
我找到了这个解决方案,它还考虑了结构体元素是否为数组。
$file_contents=file_get_contents("data.json");
$json_dump=json_decode($file_contents); 
printPath($json_dump, '', "" ,"");

function printPath($the_array, $path, $prevType) {
// Parse all elements of a structure 
// and print full PHP path to each one.
    foreach($the_array as $key => $value)  {
        if(is_array($value)) {
            // Array element cannot be directly printed: process its items as     objects:
            printPath($value, $path  . $key , "array");
        } else {            
            if (!is_object($value))  { // If the element is not an object, it can be printed (it's a leaf)
                if(is_string($value)) { 
                    $finalValue = '"' . $value . '"'; 
                } else { 
                    $finalValue = $value;
                }
                if($prevType == "array") {
                    // If array element, add index in square brackets
                    echo($path  . "["  . $key . "] =  " .  $finalValue . "<br>");
                } else {
                    echo($path . $key  . " = " . $finalValue . "<br>");                     
                }   

            } else { // else store partial path and iterate:
                if($prevType == "array") {
                    // Path of array element must contain element index:  
                    printPath($value, $path . "["  . $key . "]->"  , "dummy");
                } else {
                    printPath($value, $path . $key . "->", "dummy");
                }           
            }
        }
    }
}

示例输出:

status->connections->DSS-25->band = "X"
status->connections->DSS-25->endAt = "2019-11-20T20:40:00.000Z"
status->connections->DSS-25->startAt = "2019-11-20T12:40:00.000Z"
geometry[0]->obs[0]->name = "UDSC64"
geometry[0]->obs[0]->hayabusa2->azm = 90.34
geometry[0]->obs[0]->hayabusa2->alt = -20.51

如果有人感兴趣,这是Javascript的端口:

function iterate(obj, stack, prevType) {
    for (var property in obj) {
        if ( Array.isArray(obj[property]) ) {
            //console.log(property , "(L="  + obj[property].length + ") is an array  with parent ", prevType, stack);
            iterate(obj[property], stack  + property , "array");
        } else {
            if ((typeof obj[property] != "string")  && (typeof obj[property] != "number"))  {
                if(prevType == "array") {
                    //console.log(stack + "["  + property + "] is an object, item of " , prevType, stack);
                    iterate(obj[property], stack + "["  +property + "]." , "object");
                } else {
                    //console.log(stack +    property  , "is " , typeof obj[property] , " with parent ", prevType, stack );
                    iterate(obj[property], stack  + property + ".", "object");
                }   
            } else {
                if(prevType == "array") {
                    console.log(stack + "["  + property + "] =  "+  obj[property]);

                } else {
                    console.log(stack +    property  , " =  " ,  obj[property] );                       
                }   
            }
        }
    }
}

iterate(object, '', "File")

-1

你可以添加第三个参数,它作为字符串保存实际路径。最后,你可以将其输出。


已经尝试过了,但并不简单。请检查我刚刚编辑的代码。有什么建议吗? - Henrique

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接