2

I have one user which I will consider the root element of my tree structure. Afterwards I have an array of users coming from my database which I consider all the children elements of the tree.

The building of the tree needs to contain the following variables that determine the width and depth of the tree:

Variable: MATCHES_TREE_MAX_DEPTH (eg: 3)
Variable: MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT (eg: 2)
Variable: MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT (eg: 1)

This means that I want to create a tree structure that is depth 3 (this includes the root element, so I want 2 levels deeper). The root element has 2 children, while any children of children will have have maximum 1 child element.

The order the items are in my users array is the order I want to insert them in the tree (I want to insert them breadth first rather than depth first).

I have found the following generic function on SO: PHP - How to build tree structure list? But I don't seem to be able to adapt it to my use case, since I do not have a parent-child relationship coming from my database.

This SO answer returns me an array that is looking similar, but I'm having issues reforming it to my use case: PHP generate a tree by specified depth and rules

Example data looks like this:

Root user:

object(stdClass)[56]
  public 'user_id' => string '1' (length=1)
  public 'first_name' => string 'Dennis' (length=6)

Other users (children):

array (size=3)
  0 => 
    object(stdClass)[57]
      public 'user_id' => string '2' (length=2)
      public 'first_name' => string 'Tom' (length=3)
      public 'street' => string 'Teststreet' (length=10)
  1 => 
    object(stdClass)[58]
      public 'user_id' => string '3' (length=2)
      public 'first_name' => string 'Mary' (length=1)
      public 'street' => string 'Maryland avenue' (length=15)
  2 => 
    object(stdClass)[59]
      public 'user_id' => string '4' (length=2)
      public 'first_name' => string 'Jeff' (length=4)
      public 'street' => string 'Teststreet' (length=10)

An example of the tree I want to achieve with the examples filled (taking into account the variables of 3 max depth, 2 children of root and 1 max children of any other element than the root):

Array
(
    [userid] => 1
    [name] => "Dennis"
    [matches] => Array
        (
            [0] => Array
                (
                    [userid] => 2
                    [name] => "Tom"
                    [street] => "Teststreet"
                    [matches] => Array
                        (
                            [0] => Array
                                (
                                    [userid] => 4
                                    [name] => "Jeff"
                                    [street] => "Teststreet"
                                    [matches] = Array()
                                )
                        )
                )

            [1] => Array
                (
                    [userid] => 3
                    [name] => "Mary"
                    [street] => "Maryland avenue"
                    [matches] => Array
                        (
                        )
                )
        )
)

How do I create this tree structure given the 3 variables that determine the depth and children?

Dennis
  • 3,044
  • 2
  • 33
  • 52
  • What is the question? Do you want to create random data? – AmigoJack Jul 09 '19 at 12:10
  • If your post had an example of your "users array" then it would probably be easier to answer. I would point out that if your user array doesn't specify which users are children of other users, then you are relying on your initial variables to dictate how any given user array will be transformed into your tree structure. The data tables I've seen to define trees usually define at least 1) name, 2) parent id (0 or NULL for root), and 3) display order -- which determines the order in which children are displayed under a given parent. – S. Imp Jul 09 '19 at 13:38
  • @AmigoJack The question is how to create the tree structure as in my example. I already have my data, I updated the question to add an example of incoming data. – Dennis Jul 09 '19 at 13:44
  • @S.Imp I have updated the question to add example data. I do not have notion of "parent" id because, frankly, it doesn't matter all that much. Ie: today user A can be the "parent" of A, tomorrow it can be vice versa, in 2 days C is the parent of A, etc etc. The order in which the elements appear in the array is the order in which they should be inserted in the tree (if the breadth-first approach is taken). – Dennis Jul 09 '19 at 13:46
  • @Dennis this is a pretty weird request. If your root can only have 2 children, and those children can only have one child each, your tree can only have five total items in it before you start violating rules. What would you do with a list of 10 items? – S. Imp Jul 09 '19 at 14:07
  • @S.Imp Those 5 users will be "locked" for some time. And at the next execution of my CRON job (eg: again after 10min), a new tree will be built. Thus a new root will be chosen and 4 other possible matches (from the remaining original 10, where only 5 will remain). If I had 11 to begin with, I would have a tree with just a root element at my 3rd execution. – Dennis Jul 09 '19 at 14:16
  • @Dennis sounds like your post should specify a bit more detail about how to handle lists that exceed the bounds of your rules. Do we save the unused list items? Do we just ignore the extra items? Is the root always the first item in the list? Or are the root user and list of children provided separately? – S. Imp Jul 09 '19 at 14:21
  • @S.Imp I'm sorry if it was unclear. Your assumptions are correct. We just ignore the extra items completely. If there are more than my bounds specify, we don't even look at them. The root & children users are completely different variables. The root is an object while the children will always be an array of objects. – Dennis Jul 09 '19 at 14:24
  • @Dennis and how is it that Jeff (userid=4) is a child of both Tom and Mary? Is there some rule that items in your supplied list get more than one parent? – S. Imp Jul 09 '19 at 14:36
  • @S.Imp No! You're absolutely right, it was a mistake on my part, a user will never be duplicated in the list and can only occur once! an item gets array_shifted from the input array, gets put in the tree and a new one should get array_shifted. – Dennis Jul 09 '19 at 14:54

1 Answers1

1

EDIT: I see that the original question is looking for a solution that works with the 3 constants. I've added to the code so it's possible to define constants and loop based on them, but I am not sure I understand how one is supposed to use all three variables. In particular, MATCHES_TREE_MAX_DEPTH seems out of place given your descriptions. You indicated only two levels of children and instructed that we should skip any additional items in your list. If you want code that defines still more levels of children, you'll need to be clearer about how you want your structure to grow.

Given the very narrow limits described to this problem in the additional comments, it seems like a waste of effort to agonize over loops to handle this problem. This tree can't ever have more than five elements so an iterative solution like this should work:

// function to convert from list objects to the array we want as output
function new_obj($obj) {
    $ret = array(
        "userid" => $obj->user_id,
        "name" => $obj->first_name
    );
    if (isset($obj->street)) {
        $ret["street"] = $obj->street;
    }
    $ret["matches"] = [];
    return $ret;
}


// INPUT DATA
$root = (object)["user_id" => "1", "first_name" => "Dennis"];
$children = [
    (object)[
        "user_id" => "2",
        "first_name" => "Tom",
        "street" => "Teststreet"
    ],
    (object)[
        "user_id" => "3",
        "first_name" => "Mary",
        "street" => "Maryland avenue"
    ],
    (object)[
        "user_id" => "4",
        "first_name" => "Jeff",
        "street" => "Teststreet"
    ],
    (object)[
        "user_id" => "5",
        "first_name" => "Arthur",
        "street" => "Teststreet"
    ]

];

$result1 = new_obj($root);

// an iterative solution, only works for one trivial set of values for the constants defined
// but also does provide some insight into a more general solution
if (isset($children[0])) {
    $result1["matches"][0] = new_obj($children[0]);
}
if (isset($children[1])) {
    $result1["matches"][1] = new_obj($children[1]);
}
if (isset($children[2])) {
    $result1["matches"][0]["matches"][0] = new_obj($children[2]);
}
if (isset($children[3])) {
    $result1["matches"][1]["matches"][0] = new_obj($children[3]);
}


print_r($result1);

If you want to define constants/vars to specify child limits and then use loops, try this with the same $root and $children vars defined above.

// solution must use these constants:
define("MATCHES_TREE_MAX_DEPTH", 3);
define("MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT", 2);
define("MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT", 1);

$result2 = new_obj($root);

$i = 0;
while ($child = array_shift($children)) {
    $result2["matches"][$i] = new_obj($child);
    $i++;
    if ($i >= MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT) break;
}

$i = 0;
while ($grandchild = array_shift($children)) {
    $child = $result2["matches"][$i];
    if (count($child["matches"]) >= MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT) {
        // if we reach a child that has its max number of grandchildren, it's time to quit
        break;
    }
    // otherwise, assign this child as a grandchild
    $result2["matches"][$i]["matches"] = new_obj($grandchild);

    // increment the counter and check if we cycle back to the first child of root
    $i++;
    if ($i >= count($result2["matches"])) {
        $i = 0;
    }
}
print_r($result2);

S. Imp
  • 2,833
  • 11
  • 24