skip to Main Content

I need to reshape a bit my data. Idea is that I have array of questions, each question has their index and option array. Inside option array there is answerLabel and optional linksTo. linksto shows which question should be next. In case of no linksTo, path is over in this spot. I need to loop through data as many times to get all possible paths. So overall my data looks more or less like this:

const mockData: Tree = {
  name: "My tree",
  description: "string",
  questions: [
    {
      questionIdx: 1,
      question: "Do you like potato",
      description: "string",
      options: [
        {
          answerIdx: 1,
          answerLabel: "Yes",
          linksTo: 2,
        },
        {
          answerIdx: 2,
          answerLabel: "No",
          linksTo: 3,
        },
      ],
    },
    {
      questionIdx: 2,
      question: "Do you like carrot",
      description: "string",
      options: [
        {
          answerIdx: 1,
          answerLabel: "Yes",
          linksTo: 3,
        },
        {
          answerIdx: 2,
          answerLabel: "No",
        },
      ],
    },
    {
      questionIdx: 3,
      question: "Do you like anything",
      description: "string",
      options: [
        {
          answerIdx: 1,
          answerLabel: "Yes",
        },
        {
          answerIdx: 2,
          answerLabel: "No",
        },
      ],
    },
  ],
};

and example above needs to get that kind of shape:

const mockedOutput: PathsData = {
  name: "string",
  paths: [
    {
      pathIdx: 1,
      show: true,
      path: [
        {
          questionIdx: 1,
          question: "Do you like potato",
          answerLabel: "Yes",
          linksTo: 2,
        },
        {
          questionIdx: 2,
          question: "Do you like carrot",
          answerLabel: "Yes",
          linksTo: 3,
        },
        {
          questionIdx: 3,
          question: '"Do you like anything',
          answerLabel: "Yes",
        },
      ],
    },
    {
      pathIdx: 2,
      show: true,
      path: [
        {
          questionIdx: 1,
          question: "Do you like potato",
          answerLabel: "Yes",
          linksTo: 2,
        },
        {
          questionIdx: 2,
          question: "Do you like carrot",
          answerLabel: "Yes",
          linksTo: 3,
        },
        {
          questionIdx: 3,
          question: '"Do you like anything',
          answerLabel: "No",
        },
      ],
    },
    {
      pathIdx: 3,
      show: true,
      path: [
        {
          questionIdx: 1,
          question: "Do you like potato",
          answerLabel: "No",
          linksTo: 3,
        },
        {
          questionIdx: 2,
          question: "Do you like carrot",
          answerLabel: "Yes",
          linksTo: 3,
        },
        {
          questionIdx: 3,
          question: '"Do you like anything',
          answerLabel: "Yes",
        },
      ],
    },
    {
      pathIdx: 4,
      show: true,
      path: [
        {
          questionIdx: 1,
          question: "Do you like potato",
          answerLabel: "No",
          linksTo: 3,
        },
        {
          questionIdx: 2,
          question: "Do you like carrot",
          answerLabel: "Yes",
          linksTo: 3,
        },
        {
          questionIdx: 3,
          question: '"Do you like anything',
          answerLabel: "No",
        },
      ],
},
{
  pathIdx: 5,
  show: true,
  path: [
    {
      questionIdx: 1,
      question: "Do you like potato",
      answerLabel: "Yes",
      linksTo: 2,
    },
    {
      questionIdx: 2,
      question: "Do you like carrot",
      answerLabel: "No",
    },
  ],
},
  ],
};

I figured out that best for this case will be some recursive function which will loop over array of questions, create each path based on linksTo. In case multiple option (btw there can be more than just 2 options) it will start again and go with another options. Problem is I totally cannot figure out logic to write this function.

That’s what I got so far but I already see that probably I took wrong approach, also I cannot spot moment where to switch startAgain flag:

  function aggregateData(
      originalData: DecisionTree,
      output: PathsData,
      startAgain: boolean
    ) {
      if (!startAgain) return output;
    
      const result: PathsData = {
        ...output,
        paths: originalData.questions.map((question, i) => {
          const obj = {
            pathIdx: i,
            show: true,
            path: [
              {
                ...output.paths,
                ...{
                  questionIdx: question.questionIdx,
                  question: question.question,
                  answerLabel: question.options.map((option) => {
                    option.answerLabel;
                  }),
                  linksTo:
                    question.options.map((option) => option.linksTo) || undefined,
                },
              },
            ],
          };
          return obj;
        }),
      };
      return aggregateData(mockData, result, startAgain);
    }

2

Answers


  1. I first reshape the data for easy access to questions by their id. Then I visit each node, keeping track of the current path, while storing each complete path into the result.

    const mockData={name:"My tree",description:"string",questions:[{questionIdx:1,question:"Do you like potato",description:"string",options:[{answerIdx:1,answerLabel:"Yes",linksTo:2},{answerIdx:2,answerLabel:"No",linksTo:3},]},{questionIdx:2,question:"Do you like carrot",description:"string",options:[{answerIdx:1,answerLabel:"Yes",linksTo:3},{answerIdx:2,answerLabel:"No"},]},{questionIdx:3,question:"Do you like anything",description:"string",options:[{answerIdx:1,answerLabel:"Yes"},{answerIdx:2,answerLabel:"No"},]},]};
    
    
    // first a dictionary by index for easy access
    var questions = mockData.questions;
    var dict = questions.reduce(function(agg, item) {
      agg[item.questionIdx] = item;
      return agg;
    }, {})
    
    
    // a little wrapper for storing results
    function getAllPaths(tree) {
      var result = [];
      var index = 0;
    
      // the actual recursion to visit all possible nodes
      function visit(tree, path) {
        path = path || [];
    
        tree.options.forEach(function(option) {
          var item = {
            questionIdx: tree.questionIdx,
            question: tree.question,
            answerLabel: option.answerLabel,
          }
    
          var current = [...path, item];
    
          if (option.linksTo) {
            item.linksTo = option.linksTo;
            visit(dict[option.linksTo], current)
          } else {
            result.push({
              pathIndex: ++index,
              path: current
            })
          }
        })
      }
    
      visit(tree);
      return result;
    }
    
    
    var result = getAllPaths(questions[0]);
    console.log(JSON.stringify(result, null, 4))
    .as-console-wrapper {
      top: 0 !important;
      min-height: 100% !important
    }
    Login or Signup to reply.
  2. Before I give you the function, let’s define the interfaces that data structures. It often helps me understand(hope I didn’t make a mistake there):

    interface Option {
      answerIdx: number;
      answerLabel: string;
      linksTo?: number;
    }
    
    interface Question {
      questionIdx: number;
      question: string;
      options: Option[];
    }
    
    interface DecisionTree {
      name: string;
      description: string;
      questions: Question[];
    }
    
    interface PathNode {
      questionIdx: number;
      question: string;
      answerLabel: string;
      linksTo?: number;
    }
    
    interface Path {
      pathIdx: number;
      show: boolean;
      path: PathNode[];
    }
    
    interface PathsData {
      name: string;
      paths: Path[];
    }
    
    

    Now I was able to create the recursive function that iterates through the options and constructs the paths:

    function generatePaths(
      data: DecisionTree,
      questionIdx: number,
      answerLabel: string
    ): Path[] {
      const question = data.questions[questionIdx];
      const option = question.options.find((opt) => opt.answerLabel === answerLabel);
    
      if (!option) return [];
    
      const path: PathNode = {
        questionIdx: question.questionIdx,
        question: question.question,
        answerLabel: option.answerLabel,
      };
    
      if (option.linksTo === undefined) {
        return [{ pathIdx: 1, show: true, path: [path] }];
      }
    
      const childPaths = generatePaths(data, option.linksTo, '');
    
      return childPaths.map((childPath) => ({
        pathIdx: childPath.pathIdx,
        show: true,
        path: [path, ...childPath.path],
      }));
    }
    
    function aggregateData(data: DecisionTree): PathsData {
      const output: PathsData = {
        name: data.name,
        paths: [],
      };
    
      for (const question of data.questions) {
        for (const option of question.options) {
          const paths = generatePaths(data, question.questionIdx, option.answerLabel);
          output.paths.push(...paths);
        }
      }
    
      return output;
    }
    
    const mockedOutput: PathsData = aggregateData(mockData);
    console.log(mockedOutput);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search