Определите возможный путь с пропущенным вопросом (JavaScript)

#javascript

Вопрос:

Я создал что-то, чтобы найти возможные пути того, что пользователь может ввести в анкету. Однако логика должна немного измениться, чтобы можно было найти возможный путь даже с пропущенным вопросом.

Текущее Решение:

 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"
    }
};


class QuestionAnswerPossibilities {
    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 QuestionAnswerPossibilities(this.questions, ...this.path, andNextStep);
    }

    possibleNext() {
        const nextSteps = (this.currentQuetion.options ? 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 QuestionAnswerPossibilities(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 MatchingAnswerPaths = (answerPaths, questionsAnswered) => {
    let possiblePaths = [];

    answerPaths.forEach(answerPath => {
        let matchingPathFound = questionsAnswered.every((r, index) => {
            return answerPath[index] === r;
        });

        if (matchingPathFound) {
            possiblePaths.push(answerPath);
        }
    });

    return possiblePaths;
}

const GetLongestAnswerPath = (possiblePaths) => {
    let longest = 0;
    let longestPath = [];

    possiblePaths.forEach((path) => {
        if (path.length > longest) {
            longestPath = [path];
            longest = path.length;
        } else if (path.length == longest) {
            longestPath.push(path);
        }
    });

    if (longestPath.length > 0) {
        return longestPath[0];
    }
}

// Answered Paths
let answeredPaths = [1, 2, 3, 5];

// Get possibilities
let possibilities = QuestionAnswerPossibilities.start(originalQuestions);
possibilities = possibilities.map(agent => agent.path);
possibilities = possibilities.map(path => path.map(step => String(step)));


// Get Matched paths
let matchedPaths = MatchingAnswerPaths(possibilities, answeredPaths);

console.log(possibilities);
console.log(matchedPaths); 

Работает с:

 answeredPaths = [1, 2, 3, 4, 5];
 

Неудача с:

 answeredPaths = [1, 2, 3, 5];
 

Причины его неудачи в том, что он находит все возможности, основанные на правильном порядке, он не учитывает пропущенный вопрос, поэтому 3 -> 5 вместо стандартного порядка 3 -> 4 -> 5 ;

Возможное решение:

При пропуске от 3 до 5 я заставляю вопрос 4 быть добавленным в пути ответов. Однако я не хочу писать для этого какой-то статический код. Я хочу, чтобы совпадающие пути решили эту проблему.

Ответ №1:

Перепробовав множество решений, я наконец нашел одно, которое действительно работает:

Это также будет охватывать несколько пропусков, например

 answeredPaths = [1, 2, 3, 5];
answeredPaths = [1, 2, 3, 6];
answeredPaths = [1, 3, 6];
 

Каким был исходный код:

 const MatchingAnswerPaths = (answerPaths, questionsAnswered) => {
    let possiblePaths = [];

    answerPaths.forEach(answerPath => {
        let matchingPathFound = questionsAnswered.every((r, index) => {
            return answerPath[index] === r;
        });

        if (matchingPathFound) {
            possiblePaths.push(answerPath);
        }
    });

    return possiblePaths;
}
 

Фиксированный код:

 const MatchingAnswerPaths = (answerPaths, questionsAnswered) => {
    let possiblePaths = [];

    answerPaths.forEach(answerPath => {
        let matchingPathFound = questionsAnswered.every((r, index) => {
             let answerPathIndex = answerPath.indexOf(r); 

             return answerPath[answerPathIndex] === r;
        });

        if (matchingPathFound) {
            possiblePaths.push(answerPath);
        }
    });

    return possiblePaths;
}
 
 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"
    }
};


class QuestionAnswerPossibilities {
    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 QuestionAnswerPossibilities(this.questions, ...this.path, andNextStep);
    }

    possibleNext() {
        const nextSteps = (this.currentQuetion.options ? 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 QuestionAnswerPossibilities(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 MatchingAnswerPaths = (answerPaths, questionsAnswered) => {
    let possiblePaths = [];

    answerPaths.forEach(answerPath => {
        let matchingPathFound = questionsAnswered.every((r, index) => {
             let answerPathIndex = answerPath.indexOf(r); 

             return answerPath[answerPathIndex] === r;
        });

        if (matchingPathFound) {
            possiblePaths.push(answerPath);
        }
    });

    return possiblePaths;
}


const GetLongestAnswerPath = (possiblePaths) => {
    let longest = 0;
    let longestPath = [];

    possiblePaths.forEach((path) => {
        if (path.length > longest) {
            longestPath = [path];
            longest = path.length;
        } else if (path.length == longest) {
            longestPath.push(path);
        }
    });

    if (longestPath.length > 0) {
        return longestPath[0];
    }
}

// Answered Paths
let answeredPaths = ["1", "2", "3", "5", "6"];

// Get possibilities
let possibilities = QuestionAnswerPossibilities.start(originalQuestions);
possibilities = possibilities.map(agent => agent.path);
possibilities = possibilities.map(path => path.map(step => String(step)));


// Get Matched paths
let matchedPaths = MatchingAnswerPaths(possibilities, answeredPaths);

//console.log(possibilities);
console.log(matchedPaths);