簡體   English   中英

組成Haskell樹結構

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM