1

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 Code:

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
        }]
    },
    // Errors
    10000: {
        isError: true,
        title: "Finally, what is the first part of your postcode?",
        error: "Postcode"
    }
};

function loopPaths(currentArrayPath, questionNum, currentQuestion, paths) {

    if (questionNum > 5) {
   return false;
  }

    if (typeof currentQuestion.finalQuestion != 'undefined') {
    return false;
  } else {
    const question = currentQuestion.options;  
    const validPaths = question.filter((e) => e.nextQuestion !== 10000);   
    const clonedPath = [...paths[currentArrayPath][0]];
            
    for (var i = 0; i < validPaths.length; i++) {
      const e = validPaths[i];
            
      if (typeof paths[currentArrayPath][i] == 'undefined') {
        paths[currentArrayPath][i] = [...clonedPath];
      }
      
      paths[currentArrayPath][i].push(questionNum);
      
      loopPaths(currentArrayPath, e.nextQuestion, originalQuestions[e.nextQuestion], paths);
    }
  }
}

function possiblePaths() {
    const question1 = originalQuestions[1].options;
    const validPaths = question1.filter((e) => e.nextQuestion !== 10000);
  
  let paths = [];
  
  /*validPaths.forEach((e, i) => {
    paths[i] = [[1]];
  });*/
      
  /* 
  for (var i = 0; i < validPaths.length; i++) {
    const e = validPaths[i];
           
    loopPaths(e.nextQuestion, boilerQuestions[e.nextQuestion], paths);
  }   */
  
  // Testing with first question, first option
  paths[0] = [[1]];
  loopPaths(0, 2, originalQuestions[2], paths);
  
  console.log(paths);
}

possiblePaths();

What seems to be happening: (It's not building the correct paths and not building the different possiblities)

[
  [
    [
      1,
      2,
      3,
      4,
      5,
      4,
      5,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      4,
      5
    ],
    [
      1,
      2,
      3,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      2,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      4,
      5,
      3,
      4,
      5,
      4,
      5,
      4,
      5,
      3,
      4,
      5,
      4,
      5
    ]
  ]
]

Example of what it should show: (I'm wondering if there is an easier way to build the different possibilities from 1 -> to the final question which is 12)

[
  [
    [
      1,
      2,
      3,
      4,
      13,
      7,
      17,
      8,
      9,
      10,
      11,
      12
    ],
    [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      ....
    ]
  ]
]

How it would work:

User hits question 1 -> Presses option 1 -> User hits question 2 -> Presses option 2 -> User hits question 3 -> Presses option 1 -> User hits question 4 -> Presses option 1 -> User hits question 13

YaBCK
  • 2,949
  • 4
  • 32
  • 61

3 Answers3

4

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)

Nur
  • 2,361
  • 2
  • 16
  • 34
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(" -> "))
);
VLAZ
  • 26,331
  • 9
  • 49
  • 67
2

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
Mulan
  • 129,518
  • 31
  • 228
  • 259