[英]Composing Haskell tree structures
我在Haskell中進行遞歸思考時遇到了問題。
我正在嘗試構建一個調查應用程序,其中問題根據用戶的答案有條件地引發新問題。
我有:
- Questions
-問題列表
QuestionPaths
導致新問題的問題的問題路徑列表
- Answers
用戶的答案列表
您可以將QuestionPaths
視為元組列表,其中:
type QuestionPath = (QuestionId, AnswerChoice, NextQuestionId)
基本上這將顯示為: 如果用戶 用 AnswerChoice
回答問題 QuestionId
,然后問他 NextQuestionId
。
我試圖用多路樹 (節點可以有多個孩子)來為這個問題域建模:
data YesNo = Yes | No
data AnswerChoice = Skip
| Boolean YesNo
| ChooseOne [Text]
type Condition = AnswerChoice
data QuestionTree = QuestionNode {
question :: Question
, condition :: Condition
, userAnswer :: Maybe AnswerChoice
, children :: QuestionForest
}
type QuestionForest = [QuestionTree]
不幸的是,我現在對如何編寫像這樣組成樹的算法一無所知。
我基本上需要以下類型的函數進行組合和遍歷:
-- Constructs the tree from seed data
constructTree :: Questions -> QuestionPaths -> Answers -> QuestionTree
-- | Inserts answer to question in the tree
answerQuestion :: Question -> AnswerChoice
-- | Fetches the next unanswered question by traversing the tree.
getNextUnanswered :: QuestionTree -> Question
您能否幫助我了解構造和遍歷此類樹木的最佳方法是什么?
在這種情況下,我要做的就是將答案存儲在單獨的數據結構中- 而不是將其插入問題樹中; 將答案放在單獨的列表/集中,或文件或數據庫中,並使問題樹不可變。
為了跟蹤剩下的問題,您可以“消耗”該樹-保持程序狀態指向下一個問題,丟棄已經回答的問題(讓垃圾收集器回收它們)。
我會像這樣設計樹:
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)],然后我會通過從第一個問題的ID開始,並從Map中獲取其直接子級來構建子樹,以構建子樹。
示例(對於非常簡單的情況,其中唯一可能的答案是“是”或“否”,不允許跳過):
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結構比Id Map-> Thing更慣用地表達了程序員的意圖
從結構上保證每當相關時有一個子QuestionTree-無需執行Map.lookup,這將返回Maybe,您必須驗證其中是否包含Just(即使您知道會有下一個問題,即使這是EndOfQuestions)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.