0

I have a table in my database(mysql) with the next schema:

-----------------------------
- id  name         parent_id
-----------------------------
- 1   grandfather  NULL
- 2   father       1
- 3   uncle        1
- 4   son          2
- 5   brother      2
- 6   sister       2

And I want to show it on my page the following way:

grandfather 
father
son
brother
sister
Uncle

(a preorder traversal)

This is the best solution I had come to(doesn't work)

$sql = "SELECT p1.id, p1.name, p1.parent_id FROM tree p1 ORDER BY p1.id";
$result = $conn->query($sql);

while($row = mysqli_fetch_assoc($result)) {
        arbol($row, $result);
};
function arbol($fila, $result) {
            echo $fila["name"] . "<br>";
            $flag = false;
            while($busqueda = mysqli_fetch_row($result)) {
                if($busqueda[2]==$fila["id"]){
                    $flag = true;
                    break;
                }
            };

            if ($flag){
                foreach ($result as $ruta) {
                    if($ruta["parent_id"]==$fila["id"]){
                        arbol($ruta, $result);
                    };
                };


            } else {
                return;
            }
        };
    };

As a result of this I am getting the first traversal to the bottom but never the rest of the tree:

Grandfather
parent
son

What I am doing wrong? Or what do you suggest me?

Note: when this is finished I would have many "grandfathers" that's why there is the while (I would add a "parent_id=NULL" condition there).

Edit: the estructure of the rows after doing this:

while( $row = mysqli_fetch_assoc( $result)){
    $resguard[] = $row;
}
print_r($resguard);

is this(formated for clarity):

Array ( 
[0] => Array ( [id] => 1 [name] => Grandfather [parent_id] => ) 
[1] => Array ( [id] => 2 [name] => Parent [parent_id] => 1 ) 
[2] => Array ( [id] => 3 [name] => Uncle [parent_id] => 1 ) 
[3] => Array ( [id] => 4 [name] => Son [parent_id] => 2 ) 
[4] => Array ( [id] => 5 [name] => Brother [parent_id] => 2 ) 
[5] => Array ( [id] => 6 [name] => Sister [parent_id] => 2 ) 
)
  • Welcome to Stack Overflow. Great question. If you don't mind, can you put the rows from your DB query into a regular array and post that structure? That eliminates the DB component, which seems working, making your example more [minimal and verifiable](https://stackoverflow.com/help/mcve) and easier to write an answer to. – ggorlen Aug 19 '18 at 19:40
  • Edited the post with that information. Thanks for the welcome, being using this page for years now, but couldn't find the answer to this. – MatAle Albiach Aug 19 '18 at 22:18
  • Thanks, that's a bit easier to work with than the query object. – ggorlen Aug 19 '18 at 23:50

1 Answers1

0

Here's an example that works on your dataset and assumes the raw data is sorted by id (as your query does). I believe the way to go is to write small(er), simple(r) functions that transform the data into a tree and to traverse it step by step. It's probably possible make a traversal happen from the array and print it in one leap as you're doing, but my brain isn't able to think of a good way at the moment.

function make_tree($data) {
    $tree = [];

    foreach ($data as $node) {
        insert($tree, $node);
    }

    return $tree;
}

function insert(&$root, &$node) {
    if (!$root) {
        $root = $node;
    }
    else if ($root["id"] === $node["parent_id"]) {
        $root["children"][] = $node;
    }
    else if (array_key_exists("children", $root)) {
        foreach ($root["children"] as &$c) {
            if (insert($c, $node)) {
                break;
            }
        }
    }
}

function preorder(&$root) {
    if ($root) {
        yield $root;

        if (array_key_exists("children", $root)) {
            foreach ($root["children"] as $c) {
                yield from preorder($c);
            }
        }
    }
}

$data = [
    ["id" => 1, "name" => "Grandfather", "parent_id" => null],
    ["id" => 2, "name" => "Father", "parent_id" => 1],
    ["id" => 3, "name" => "Uncle", "parent_id" => 1],
    ["id" => 4, "name" => "Son", "parent_id" => 2],
    ["id" => 5, "name" => "Brother", "parent_id" => 2],
    ["id" => 6, "name" => "Sister", "parent_id" => 2]
];

$tree = make_tree($data);

foreach (preorder($tree) as $node) {
    echo $node["name"]."\n";
}

Output:

Grandfather
Father
Son
Brother
Sister
Uncle
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Thanks you very much, I am gonna test this tomorrow after my coffee, I can't rigth now, it seems so clean. My only doubt is what happens if I charge the father after his childrens. Like if, I have "son" with id=1 and "father" with id=2. won't it skip the father? Maybe ordering by parent_id would be the solution? – MatAle Albiach Aug 20 '18 at 03:03
  • @MatAleAlbiach That's right--looking at your query again, I see it's sorted by `id` rather than `parentId`, so you'd want to make that change. Feel free to let me know if I missed anything or this isn't doing the trick, preferably with a counterexample and I'll update. – ggorlen Aug 20 '18 at 04:01
  • Tested it today, this solved the problem pretty much. The tree is looking great, but I have come to another problem. It draws almost all the tree, but I have cases of people that have a parent_id bigger than their parent's parent_id. Example: father id is 1, grandfather id is 100. So if I order by parent ID the son will be skiped. I can't really think of a way that solve this and also the problem that Id order causes... – MatAle Albiach Aug 20 '18 at 19:12
  • Hmm, possibly you need a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph) if it's a case of complex dependencies. It could be involved, or it could be a simple tweak, I'd have to think about it. I'm not fully clear on your counterexample, it seems like father would be in a row after grandfather because its `parent_id` is 100 which is `>` grandfather id (null?) and would be able to be inserted properly. Feel free to post a new question with your latest code with a stab at trying to solve the edge case(s) you have and clear examples--that might make it easier to resolve going forward. – ggorlen Aug 20 '18 at 19:20
  • I have posted the question, it's really specific so I would really apreciatte if yo take a look. And thanks again. https://stackoverflow.com/questions/51938048/cant-get-to-show-all-of-a-tree-in-a-traversal-preorder – MatAle Albiach Aug 20 '18 at 20:26