Что бы я сделал в таком случае, это чтобы сохранить ответы в отдельной структуре данных - не , чтобы вставить их в дерево вопросов; поместите ответы в отдельный список / набор, либо в файл или базу данных, и пусть дерево вопросов будет неизменным.
Чтобы отслеживать, какие вопросы еще предстоит задать, вы можете «потреблять» дерево - сохраняйте состояние вашей программы, указывая на следующий вопрос, отбрасывая вопросы, на которые уже даны ответы (позволяя сборщику мусора вернуть их).
Я бы спроектировал дерево так:
data AllowedAnswers = YesOrNo {
ifUserAnsweredYes :: QuestionTree,
ifUserAnsweredNo :: QuestionTree
}
| Choices [(Text, QuestionTree)]
data QuestionTree = Question {
description :: Text
, allowedAnswers :: AllowedAnswers
, ifUserSkipsThisQuestion :: QuestionTree
}
| EndOfQuestions
Обратите внимание на несколько вещей:
Вам не нужно беспокоиться о нескольких возможных путях, ведущих к одному и тому же вопросу - вы можете разместить один и тот же узел QuestionTree в нескольких местах, и он будет использоваться совместно (Haskell не будет создавать несколько его копий)
В этом дизайне нет места для хранения ответов пользователя - они хранятся в другом месте (то есть где-то в списке или в файле) - нет необходимости изменять дерево вопросов.
Когда пользователь отвечает на вопросы, просто переместите «указатель» к следующему дереву QuestionTree, в зависимости от ответа пользователя.
Что касается «как построить это дерево из списка (QuestionId, AnswerChoice, NextQuestionId)» - я думаю, что сначала я бы преобразовал его в карту: `` `Map QuestionId [(AnswerChoice, Maybe QuestionId)], затем Я построил бы дерево, начав с идентификатора первого вопроса и выбрав его непосредственных потомков с карты, построив поддеревья.
Пример (для очень упрощенного случая, когда единственно возможными ответами являются «да» или «нет», без пропуска):
buildTree questionMap questionId = case Map.lookup questionId questionMap of
Nothing -> EndOfQuestions
Just (description, [("yes", nextQuestionIdIfYes), ("no", nextQuestionIdIfNo)]) ->
Question { description = description
, allowedAnswers = YesOrNo {
ifUserAnsweredYes = buildTree questionMap nextQuestionIdIfYes
, ifUserAnsweredNo = buildTree questionMap nextQuestionIdIfNo
}
, ifUserSkipsThisQuestion = EndOfQuestions
}
Если вам интересно, "почему бы просто не использовать Карту напрямую?" - да, вы могли бы (и часто это будет правильным решением), но подумайте:
Структура QuestionTree выражает намерение программиста более идиоматически, чем Map of Id -> Thing
Структурно гарантируется наличие дочернего QuestionTree, когда это уместно - нет необходимости делать Map.lookup, который будет возвращать значение Maybe, которое вы должны проверить, которое содержит Just (даже если вы знаете будет следующий вопрос, даже если это EndOfQuestions)