Skip to content Skip to sidebar Skip to footer

Get Possible Question Answer Paths (JavaScript)

I'm looking to get all the possible question answer paths based on question options, I've been racking my brain all day and can't seem to figure out why my code isn't working. Test

Solution 1:

You can use a recursive generator fuction.

const originalQuestions = { 1: { title: "Title", firstQuestion: true, options: [{ tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 10000 }] }, 2: { title: "Title", options: [{ tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }] }, 3: { title: "Title", options: [{ tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }] }, 4: { title: "Title", options: [{ tooltip: "", nextQuestion: 13 }, { tooltip: "", nextQuestion: 5 }] }, 5: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 10000 }] }, 6: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 14 }] }, 7: { title: "Title", options: [{ tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }] }, 8: { title: "Title", options: [{ tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }] }, 9: { title: "Title", options: [{ tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }] }, 10: { title: "Title", options: [{ tooltip: "", nextQuestion: 11 }, { value: "Roof", attribute: "Flue Exit", tooltip: "", nextQuestion: 15 }] }, 11: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 12: { finalQuestion: true, input: true, placeHolder: 'e.g SWS' }, 13: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }] }, 14: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 10000 }] }, 15: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 17: { title: "Title", options: [{ tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }] }, 10000: { isError: true, title: "Finally, what is the first part of your postcode?", error: "Postcode" } };

function* fnName(key, result = [key]) {
    let options = originalQuestions[key]?.options;
    if (!options) yield result
    else for (const id of new Set(options.map(v => v.nextQuestion)))
        yield* fnName(id, result.concat(id));
}
console.log("Prettify", Array.from(fnName(1), path => path.join(", ")))

I simple pass 1st question id (fnName(1)), in this case its number 1 Because this is the 1st question. (originalQuestions[1].firstQuestion = true)


Solution 2:

Send some agents that will explore your paths. Each agent keeps track of where it has been. It can also clone itself in order to explore on multiple paths:

class Agent {
  constructor(questions, ...path){
    //data fields
    this.questions = questions;
    this.path = path;
    
    //derived fields
    const currentStep = this.path[this.path.length-1];
    this.currentQuetion = this.questions[currentStep];
    this.isFinished = 
      this.currentQuetion.finalQuestion
      || this.currentQuetion.isError
      || false;
  }
  
  clone(andNextStep) {
    return new Agent(this.questions, ...this.path, andNextStep);
  }
  
  possibleNext() {
    const nextSteps = (this.currentQuetion.options ?? [])
      .map(x => x.nextQuestion);
      
    return new Set(nextSteps);
  }
  
  takeStep() {
    if (this.isFinished)
      return [this];
      
    return Array.from(this.possibleNext(), step => this.clone(step));
  }
  
  static start(questions) {
    //find any possible first questions
    const first = Object.entries(questions)
      .filter(([key, q]) => q.firstQuestion);
    
    //for each first question create an agent
    let pathExplorers = first.map(([start]) => new Agent(questions, start));
    
    //get all agents to continue until they are all finished
    while (pathExplorers.some(x => !x.isFinished))
      pathExplorers = pathExplorers.flatMap(x => x.takeStep());
    
    return pathExplorers;
  }
}

const originalQuestions = { 1: { title: "Title", firstQuestion: true, options: [{ tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 10000 }] }, 2: { title: "Title", options: [{ tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }] }, 3: { title: "Title", options: [{ tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }] }, 4: { title: "Title", options: [{ tooltip: "", nextQuestion: 13 }, { tooltip: "", nextQuestion: 5 }] }, 5: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 10000 }] }, 6: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 14 }] }, 7: { title: "Title", options: [{ tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }] }, 8: { title: "Title", options: [{ tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }] }, 9: { title: "Title", options: [{ tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }] }, 10: { title: "Title", options: [{ tooltip: "", nextQuestion: 11 }, { value: "Roof", attribute: "Flue Exit", tooltip: "", nextQuestion: 15 }] }, 11: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 12: { finalQuestion: true, input: true, placeHolder: 'e.g SWS' }, 13: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }] }, 14: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 10000 }] }, 15: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 17: { title: "Title", options: [{ tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }] }, 10000: { isError: true, title: "Finally, what is the first part of your postcode?", error: "Postcode" } };

const result = Agent.start(originalQuestions)
  .map(agent => agent.path);
  
console.log(
  //more concise printing
  result.map(path => path
    .map(step => String(step).padStart(2))
    .join(" -> "))
);

Solution 3:

Nur's wonderful answer is almost exactly how I would solve this. I might suggest a few changes that makes this different enough to warrant a separate post -

function* paths(t, key) {
  const { options } = t[key] ?? {}
  if (options == null) return yield [key]
  for (const id of new Set(options.map(v => v.nextQuestion)))
    for (const path of paths(t, id))
      yield [key, ...path]
}

const originalQuestions =
  { 1: { title: "Title", firstQuestion: true, options: [{ tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 2 }, { tooltip: "", nextQuestion: 10000 }] }, 2: { title: "Title", options: [{ tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }, { tooltip: "", nextQuestion: 3 }] }, 3: { title: "Title", options: [{ tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }, { tooltip: "", nextQuestion: 4 }] }, 4: { title: "Title", options: [{ tooltip: "", nextQuestion: 13 }, { tooltip: "", nextQuestion: 5 }] }, 5: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 10000 }] }, 6: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 14 }] }, 7: { title: "Title", options: [{ tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }, { tooltip: "", nextQuestion: 17 }] }, 8: { title: "Title", options: [{ tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }, { tooltip: "", nextQuestion: 9 }] }, 9: { title: "Title", options: [{ tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }, { tooltip: "", nextQuestion: 10 }] }, 10: { title: "Title", options: [{ tooltip: "", nextQuestion: 11 }, { value: "Roof", attribute: "Flue Exit", tooltip: "", nextQuestion: 15 }] }, 11: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 12: { finalQuestion: true, input: true, placeHolder: 'e.g SWS' }, 13: { title: "Title", options: [{ tooltip: "", nextQuestion: 6 }, { tooltip: "", nextQuestion: 6 }] }, 14: { title: "Title", options: [{ tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 7 }, { tooltip: "", nextQuestion: 10000 }] }, 15: { title: "Title", options: [{ tooltip: "", nextQuestion: 12 }, { tooltip: "", nextQuestion: 12 }] }, 17: { title: "Title", options: [{ tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }, { tooltip: "", nextQuestion: 8 }] }, 10000: { isError: true, title: "Finally, what is the first part of your postcode?", error: "Postcode" } }

for (const path of paths(originalQuestions, 1))
  console.log(path.join(" -> "))
.as-console-wrapper { min-height: 100%; }
1 -> 2 -> 3 -> 4 -> 13 -> 6 -> 7 -> 17 -> 8 -> 9 -> 10 -> 11 -> 12
1 -> 2 -> 3 -> 4 -> 13 -> 6 -> 7 -> 17 -> 8 -> 9 -> 10 -> 15 -> 12
1 -> 2 -> 3 -> 4 -> 13 -> 6 -> 14 -> 7 -> 17 -> 8 -> 9 -> 10 -> 11 -> 12
1 -> 2 -> 3 -> 4 -> 13 -> 6 -> 14 -> 7 -> 17 -> 8 -> 9 -> 10 -> 15 -> 12
1 -> 2 -> 3 -> 4 -> 13 -> 6 -> 14 -> 10000
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 17 -> 8 -> 9 -> 10 -> 11 -> 12
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 17 -> 8 -> 9 -> 10 -> 15 -> 12
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 14 -> 7 -> 17 -> 8 -> 9 -> 10 -> 11 -> 12
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 14 -> 7 -> 17 -> 8 -> 9 -> 10 -> 15 -> 12
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 14 -> 10000
1 -> 2 -> 3 -> 4 -> 5 -> 10000
1 -> 10000

Post a Comment for "Get Possible Question Answer Paths (JavaScript)"