简体   繁体   中英

The most elegant way to walk trees in PHP

I have trees like this one:

$tree = array("A", array(
            array("B", 1),
            array("C", 2),
            array("D",
                array("E",
                    array("F")),
                array("G")),
            array("H", 3)));

Each node is an array, type of node is its first element and other elements are node's arguments (they can be list of other nodes, single node, some value etc.; node can have no argument, one argument or more arguments).

What do you think is the most elegant way to walk these type of trees?

I came up with two possibilities:

1) using switch statement

/*
 * + shorter
 * + fall-througs (easy way to handle more nodes with same code)
 *
 * - worse readability
 */
function my_tree_walker($tree)
{
    switch ($tree[0]) {
        case 'A':
            list($_, $subnodes) = $tree;
            $ret = '';

            foreach ($subnodes as $subnode) {
                $ret .= my_tree_walker($subnode);
            }

            return $ret;

        break;
        case 'B': /*...*/ break;
        case 'C': /*...*/ break;
        case 'D': /*...*/ break;
        case 'E': /*...*/ break;
        case 'F': /*...*/ break;
        case 'G': /*...*/ break;
        case 'H': /*...*/ break;
    }
}

2) object with method for each node type

/*
 * + better readability
 * + more declarative
 *
 * - longer
 * - `new static` is PHP >=5.3 only
 */

abstract class TreeWalker
{
    protected function __construct(){}

    final protected function walk($node)
    {
        $nodetype = array_shift($node);
        return call_user_func_array(array($this, 'walk' . $nodetype), $node);
    }

    public static function w($tree)
    {
        $instance = new static;
        return $instance->walk($tree);
    }
}

final class MyTreeWalker extends TreeWalker
{
    protected function __construct()
    {
        // initialize
    }

    private function walkA($subnodes)
    {
        $ret = '';

        foreach ($subnodes as $subnode) {
            $ret .= $this->walk($subnode);
        }

        return $ret;
    }

    private function walkB($n) { /*...*/ }
    private function walkC($n) { /*...*/ }
    private function walkD($subnode) { /*...*/ }
    private function walkE() { /*...*/ }
    private function walkF() { /*...*/ }
    private function walkG() { /*...*/ }
    private function walkH($n) { /*...*/ }
}

Or do you suggest even more elegant way of walking trees?

I also considered nodes being objects and instead of separate tree walkers each node would have methods for walks inside. However I think it will make code harder to maintain, because parts of walkers' code will be placed in different locations and it will be harder to use same code for more nodes.

I think simplicity is elegant.

No need to reinvent the wheel! PHP comes equipped with SPL (Standard PHP Library) which offers several Iterators that can do all the work for you.

A few to check out would be RecursiveIteratorIterator and RecursiveArrayIterator

I combined both ways and created DSL:

A ($subnodes) {
    $ret = '';
    foreach ($subnodes as $subnode) {
        $ret .= WALK($subnode);
    }

    return $ret;
}
B ($n) { /*...*/ }
C ($n) { /*...*/ }
D ($subnode) { /*...*/ }
E () { /*...*/ }
F () { /*...*/ }
G () { /*...*/ }
H ($n) { /*...*/ }

that is translated to PHP.

I recommend this library: https://packagist.org/packages/lukascivil/treewalker

TreeWalker is a simple and small Library that will help you to work faster with manipulation of structures in PHP

  • getdiff() - Get json difference
  • walker() - Edit json (Recursively)
  • structMerge() - Joins two structures
  • createDynamicallyObjects() - Create nested structure by Dynamic keys
  • getDynamicallyValue() - Dynamically get a structure property
  • setDynamicallyValue() - Dynamically access a structure property to set a value

I made a simple recursive function that effectively walks a tree. Here it is

   function deep_cetegories($categories){
     foreach($categories as $category)
  {
    print_r((json_encode($category['category_name'])));
    if(isset($category['children']))
    {
        deep_cetegories($category['children']);
    }

  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM