bobsoap
bobsoap

Reputation: 5104

How would I best construct a recursive tree visual with unknown child nodes in PHP?

I'm having trouble getting started with the following:

I'm trying to create a multi-level tree visualization of the entire relationship path without knowing the number of levels. How would I best do this?

Here is an example of the data array (simplififed):

array(
   1 => array( //this key is the answer id
      question_id = 1,
      answers = array(
         0 => answer_opens_question__id = 2,
         1 => answer_opens_question__id = 3,
         2 => answer_opens_question__id = 5
      )
   ),

   2 => array( 
      question_id = 2,
      answers = array(
         0 => answer_opens_question__id = 1,
         1 => answer_opens_question__id = 5
      )
   ),

   5 => array( 
      question_id = 3,
      answers = array(
         0 => answer_opens_question__id = 2
      )
   )
)

In theory, this could go on forever and end up in an infinite loop - it's highly unlikely that this will ever happen though, since this data is user generated and not dynamically created.

I'm only looking for a way to recursively display a path like question > answer>>question > answer>>question > answer>>question> ... - the output will be in HTML with nested ul-li.

Any ideas on how I could do this? Thanks a lot for input of any kind.


EDIT/Solution:

Thanks to DaveRandom, I got it: write a function that loops through each answer and calls itself for each.

function recursiveTree($id) {               
                global $data; //an array like the one above
                if(array_key_exists($id, $data)) {
                    echo '<ul><li>';                        
                    echo $data[$id]['question_name']; //question

                    if(is_array($data[$id]['answers']) && !empty($data[$id]['answers'])) {

                        foreach($data[$id]['answers'] as $answer) {
                            echo '<ul><li class="answer"><div>'.$answer['text'].' opens </div>';
                            recursiveTree($answer['answer_opens_question__id']); //call this very function for this answer's question - that's the trick
                            echo '</li></ul>';
                        }
                    }                           
                    echo '<li></ul>';
                }                   
            }//recursiveTree()

recursiveTree(1); //where 1 is the first question's id

I'm not keeping track of questions yet like Dave is in this code (please check out the accepted answer below) - that will be added. It works like this already though (because I don't have any loops in my test data), but I agree with everybody that it's advisable to take care of possible infinite loops.

With some padding and some borders around li.answer, the above output is even readable :)

Thanks again DaveRandom.

Upvotes: 0

Views: 339

Answers (2)

DaveRandom
DaveRandom

Reputation: 88657

Maybe this will give you a prod in the right direction? (Slightly fixed)

function recursive_tree ($startQuestionId, $alreadyDisplayed = array()) {
  // Make sure we only display each question once
  $alreadyDisplayed[] = $startQuestionId;
  // Replace this with a sensible query to get the answers for question id $startQuestionId
  $answers = $db->query("SELECT answers.answerId, answers.opensQuestionId FROM questions, answers WHERE questions.questionId = '$startQuestionId' AND answers.questionId = questions.questionId");
  // Echo a header for the question
  echo "<div id='question$startQuestionId'>Question $startQuestionId\n<ul>\n";
  while ($row = $db->fetch()) {
    // Loop through the answers to this question
    echo "<li>\nAnswer id {$row['answerId']} opens question id {$row['opensQuestionId']}\n";
    if (!in_array($row['opensQuestionId'],$alreadyDisplayed)) {
      // The linked question hasn't been displayed, show it here
      $alreadyDisplayed = array_merge($alreadyDisplayed,recursive_tree($row['opensQuestionId'],$alreadyDisplayed));
    } else {
      // The linked question has already been displayed
      echo "(<a href='#question{$row['opensQuestionId']}'>Already displayed</a>)\n";
    }
    echo "</li>\n";
  }
  // Close everything off
  echo "</ul>\n</div>\n";
  return $alreadyDisplayed;
}

// And call it like this
$questionToStartAt = 1;
recursive_tree($questionToStartAt);

Upvotes: 1

Marc B
Marc B

Reputation: 360702

You do have a loop in that data. Question #1 has an answer pointing at question #2, and question #2 has an answer pointing at question #1. Ditto for #5 and #2.

If you want to prevent an infinite loop, you'll just have to build up a parallel breadcrumb array as you recurse down the tree. if you come to question #2 and see that it's pointing at question #1 - well, the breadcrumb array indicates that #1's been output already, so you skip recursing down that particular branch, and no loop occurs.

Upvotes: 2

Related Questions