开发者

Walk array recursively and print the path of the walk

Can someone help me with some code or instructions on how to walk recursively an array and when reaching the last element print the full path to it? A simple echo will work because I will adapt the code to some other function I'm developing.

The function doesn't need to figure the array dimension because this param will be passed:

Example:

$depth = 8;

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

When function reachs the 8th element it print all the path to it:

//print path
'1 -> 3 -> 5 -> 6 -> 9 -> 5 -> 8 -> 9'
  • As I said, only printing in this format will work cause I will implement the code into some other function.

  • array keys can have the same value. Obviously not the same value in the same sequence for the entire arary.

Updated:

Walk recursively function:

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

Will print:

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

Now, how can I also print the path of the array everytime it reaches the end? For example:

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

EDITED CODE ADDING A THIRD PARAM TO RECORD THE PATH:

$someArray[1][2][3] = 'end';
$someArray[1][2][6] = 'end';
$som开发者_运维技巧eArray[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);

Will print:

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, 


You could employ a RecursiveIteratorIterator (docs) to take the hard work out of recursing through the arrays.

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


This example is to give you idea, not to solve the actual task.

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

If no match is found, null is returned.


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


I just wrote a function that makes recursive looping a bit easier: Similar to array_walk_recursive but with some extra functionality

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

Called like so:

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

Setting recursive to false will only evaluate the first level.

Setting recursive to true will cause it to traverse the entire array.

Setting recursive to an integer will cause it to only traverse to that depth.

Walkable functions can be referenced or passed to callback as anonymous function.

(expects: value, key, custom, info) The returned value replace the current value.

Custom data can be passed and some additional info is provided for you.

You can expand on the walk function if you need additional info.


I had similar problem. Here is a Depth-First Search-ish solution(no path depth included, it reaches until the very end of the array). Comment the 'if' statement if u don't want to include the value:

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


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

Check code here


I came up with the following function based on @salathe's one. It returns an array where each element is an array containing the leaf at index 0 and the array of the path keys at index 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;
}

The main reason I implemented this one is that $iterator->hasChildren() returns true if the current iterated leaf is an object. Therefore, I wouldn't be able to get the path of it that way.


I found this solution, which also keeps into account if elements of the structure are arrays:

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

Example output:

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

In case anybody is interested, here it is the port to 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")


You can add a third parameter which holds the actual path as String. At the end you can output it then.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜