tags:

views:

94

answers:

3

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.

+3  A: 

Take a look at:

Artefacto
The problem is all nodes won't be processed the same. There will still be need for switch or something. Or am I missing something?
Jakub Kulhan
@Jak The traversal itself is independent of what the data structures, as long as they're iterable. If you want to treat the elements differently, yes, you'll still need a switch statement or you can use objects instead of arrays and take advantage of dynamic dispatch.
Artefacto
@Artefacto As I wrote in my question I considered objects and I think they would be more harm than good.
Jakub Kulhan
+2  A: 

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

Anthony Forloney
The problem is all nodes won't be processed the same. There will still be need for switch or something. Or am I missing something?
Jakub Kulhan
Can you provide an example of what you expect to do with the nodes so we have a better understanding of what you are trying to accomplish?
Anthony Forloney
@Anthony Forloney: I'm trying to refactor tree walkers (like these: http://github.com/jakubkulhan/phpeg/blob/master/lib/phpgen.php#L506, http://github.com/jakubkulhan/phpeg/blob/master/lib/phpgen.php#L551, http://github.com/jakubkulhan/phpeg/blob/master/lib/phpgen.php#L195 and following methods that start with 'g') in my PEG generator . I want to make them separate (separate class/function) and to use same language constructs.
Jakub Kulhan
A: 

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.

Jakub Kulhan