简体   繁体   中英

Building a tree from transversing an array in PHP

How to build a tree from an array in PHP knowing the rules that need to be followed to build the tree?

I don't have any particular place in the code where I can find what's really wrong
Anyway, the problems are probably related with the passing by reference I'm repeatedly using.
I did not use recursion because speed is really really important here. From the rules that the input array has (I have full control over them), this is possible, and faster, without recursion.

How it works: As you transverse the array, each element's ['start_order'] is a bigger number than the previous ['start_order'].
Every time that the next element's ['start_order'] is bigger than this element's ['start_order'] and the next element's ['end_order'] is smaller than this element's ['end_order'], then that element is a child of this element .
After that step, I must find all the children of that element (the one I just found that it it this element's child).

Here's my code.

<?php
ksort($treeArray);
$tree = array();
$stack = array();
reset($treeArray);
$currentParent = &$treeArray[key($treeArray)];
$tree[] = &$treeArray[key($treeArray)];

while(next($treeArray) !== false){

    if(current($treeArray)['start_order'] <= $currentParent['end_order']){
        if(current($treeArray)['end_order'] <= $currentParent['end_order']){
            // There's a child of the previous

            // push the new parent
            $stack[] = $currentParent;

            if(!isset($currentParent['children'])){
                $currentParent['children'] = array();
            }
            $currentParent['children'][] = &$treeArray[key($treeArray)];
            $currentParent = current($treeArray);

        }else{
            // Supposed not to happen. Log the problem.
        }
    }else /* if(current($treeArray)['start_order'] > $currentParent['end_order']) */{
        // Pop the child here, there are no more children.
        if(($popedElement = array_pop($stack)) === NULL){
            $tree[] = $currentParent;
        }else{
            $popedElement['children'][] = &$treeArray[key($treeArray)];
            $stack[] = $popedElement;
            $currentParent = current($treeArray);
        }
    }
}

?>

Example:

The input array of this can be something that structurally looks like this:

[1][child1][child1child1][child2][child2child1][child2child2][child2child3][2][child2child1]

which resolves into this tree:

[1]
    [child1]
        [child1child1]
    [child2]
        [child2child1]
        [child2child2]
        [child2child3]
[2]
    [child2child1]

And don't forget that this order maintains. [child2child3] never appears before [child2child2], [2] never appears before [1]. In this analogy, is almost like when dealing with XML.

Found the problem here. The problem is related on how I treat the pass-by-reference while trying to solve this problem.

Here's the solution:

$tree[] = &$treeArray[key($treeArray)];
$currentParent = &$treeArray[key($treeArray)];
next($treeArray);

while(current($treeArray) !== false){

    if(current($treeArray)['start_order']['start_position'] <= $currentParent['end_order']['end_order']){
        if(current($treeArray)['end_order']['end_order'] <= $currentParent['end_order']['end_order']){
            // There's a child of the previous

            // push the new parent
            $stack[] = &$currentParent;

            $currentParent['children'][] = &$treeArray[key($treeArray)];

            $currentParent = &$treeArray[key($treeArray)];
        }else{
            // Supposed not to happen. Log the problem.
        }
        next($treeArray);
    }else /* if(current($treeArray)['start_order']['start_position'] > $currentParent['end_order']['end_order']) */{
        // Close previous element here. There are no more children.

        if(end($stack) === false){
            $currentParent = &$treeArray[key($treeArray)];
            $tree[] = &$treeArray[key($treeArray)];

            next($treeArray);
        }else{              
            $currentParent = &$stack[key($stack)];
            unset($stack[key($stack)]);
        }
    }
}

The main problem was actually the pass-by-reference which is different in PHP than it is in C.
To solve this problem, I'm unable to use both push or pop php functions:
The push because the $var[] operator is faster and using that function.
The pop, because its return is a copy of what I had pushed before instead of the actual element I had pushed.

Additionally, all variable assignments have to be explicitly made by reference (using the & character), so using current() for assignment is out of the question, instead I need to use the key() function like I did.

I also had a small but important bug that forced me to change the "while" instruction. It now contains current() instead of next(). That's only because after the pop of the stack I mustn't move to the next element. I need to do another iteration before I move to the next one. This solves the bad nesting generated when there are multiple tags closing in the same place.

Please note that this code is not optimized for speed .

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