在Haskell中从文件中读取树

时间:2014-11-30 18:30:28

标签: haskell tree binary-tree

我想从Haskell中的文件创建一个树。为此,我将文件读入此列表列表:

列表中每个元素的名称遵循以下模式:

["Name","Dad","Mum"]

[["Bob","Dylan","Susan"],
 ["Dylan","Cole","Sarah"],
 ["Cole","Patrick","Patricia"],
 ["Sarah","David","Fiona"],
 ["Susan","Michael","Madeline"]]

所需的输出类似于:

Bob
      Dylan
            Cole
                  Patrick
                  Patricia
            Sarah
                  David
                  Fiona
      Susan
            Michael
            Madeline

空格可以是一个标签,我只是更多地强调我的观点。

以下是我迄今为止所做的事情:

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) 

singleton :: a -> Tree a  
singleton x = Node x EmptyTree EmptyTree  

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node a left right)   
    | x == a = Node x left right  
    | x < a  = Node a (treeInsert x left) right  
    | x > a  = Node a left (treeInsert x right)  

createTree :: (Ord a) => [a] -> Tree a
createTree [] = EmptyTree
createTree (x:xs) = createTree2 (Node x EmptyTree EmptyTree) xs
  where
    createTree2 tree [] = tree
    createTree2 tree (y:ys) = createTree2 (treeInsert y tree) ys

printTree :: Show a => Tree a -> IO ()
printTree = (mapM_ putStrLn) . treeIndent
  where
    treeIndent EmptyTree          = ["\nEmpty Tree\n"]
    treeIndent (Node v lb rb) =
      [(show v)] ++
      map ("      " ++) ls ++
      ("" ++ r) : map ("   " ++) rs
    where
        (r:rs) = treeIndent $ rb
        ls     = treeIndent $ lb

所有这些让我,基本上创建树,并将其打印到屏幕上。我正在努力的是根据这个问题正确创建树。

3 个答案:

答案 0 :(得分:1)

考虑问题的更一般版本可能更简单。即,考虑类型[(a, Maybe a, Maybe a)]的列表。您可以通过将每个第一个元素作为一个节点来构建一个树(更具体地说,一个树列表),第二个和第三个元素对应于分支 - 如果它们是Nothing,则分支是Nil。否则,此函数的语义完全对应于您要写入的语义。

首先,编写一个辅助函数来编码这个逻辑:

lookupDef :: Eq a => Maybe a -> [(a, Tree a)] -> Tree a
lookupDef Nothing   _ = Nil 
lookupDef (Just a) xs | Just r <- lookup a xs = r 
                      | otherwise             = Node a Nil Nil

第二个论点是对应于其余名称的树的现有(键,值)对列表。然后,如果要查找的值不是其中一个键,那么它就是一个“终端”值,所以只需在树中单独返回它。

然后,构成上述(键,值)对列表的中间函数。

readTreeList :: Eq a => [(a, Maybe a, Maybe a)] -> [(a, Tree a)]
readTreeList [] = []
readTreeList xs@(_:_) = result where
 result = [ (p, Node p ? ?) | (p, l, r) <- xs ] 

以上内容应该是显而易见的:输入列表中的每个键都必须与输出中的值相对应。该密钥的树将为Node p q r,其中q / r是与l / r对应的树。上面的第一个函数将计算q和r。这是lookupDef函数的来源:

 result = [ (p, Node p (lookupDef l ?) (lookupDef r ?)) | (p, l, r) <- xs ] 

但是查找子树的列表是什么?我们唯一的这样的列表是result,所以让我们尝试:

 result = [ (p, Node p (lookupDef l result) (lookupDef r result)) 
          | (p, l, r) <- xs ] 

通过懒惰的魔力,这实际上会起作用。

然后从中获取单个树,只需获取结果的第一个元素(您的样本输入指示第一个元素应该用作根)。实际上,您可以使用以上内容来内联此部分:

readTree :: Eq a => [(a, Maybe a, Maybe a)] -> Tree a 
readTree [] = Nil 
readTree xs@(_:_) = snd h where 
  result@(h:_) = [ (p, Node p (lookupDef l result) (lookupDef r result)) 
                 | (p, l, r) <- xs ] 

然后你的数据:

test = map (\([x,y,z]) -> (x, Just y, Just z))
  [["Dylan","Cole","Sarah"],
   ["Sarah","David","Fiona"],
   ["Bob","Dylan","Susan"],
   ["Cole","Patrick","Patricia"],
   ["Susan","Michael","Madeline"]]

结果:

>printTree $ readTree test
"Bob"
  |"Dylan"
  |  |"Cole"
  |  |  |"Patrick"
  |  |  |"Patricia"
  |  |"Sarah"
  |  |  |"David"
  |  |  |"Fiona"
  |"Susan"
  |  |"Michael"
  |  |"Madeline"

对于除存储键值对(Data.Map)的列表之外的数据类型,这肯定会更快,但这是一个不同的问题。


请注意,我稍微修改/添加了定义,但这与上面的代码无关:

{-# LANGUAGE DeriveFunctor, DeriveFoldable #-}

import qualified Data.Foldable as F

data Tree a = Nil | Node a (Tree a) (Tree a) 
  deriving (Show, Read, Eq, Functor, F.Foldable) 

这会为您提供fmaptoList

formatTree Nil = Nil
formatTree (Node a l r) = Node (show a) 
                               (fmap ("  |" ++) $ formatTree l)
                               (fmap ("  |" ++) $ formatTree r)

printTree x = putStrLn . unlines . F.toList . formatTree $ x 

这为您提供了更简单的漂亮打印功能。

答案 1 :(得分:1)

如果我理解正确,您遇到此问题的两个部分问题:创建树,并以所需的样式打印它。我将解决其中的每一个问题:

创建树

这个问题的皱纹是输入数据以我将称之为关联列表的形式出现,它将每个父节点与两个子节点相关联。此列表限制了如何构建树,但根据这些约束可能不会立即显示如何继续(有趣的是,它们没有指定唯一树)。我使用您的Tree类型编写了此函数来执行此操作:

data Tree a = EmptyTree | Node a (Tree a) (Tree a)

toTree :: [[String]] -> Tree String
toTree list = toTree' root
  where
    -- both these are extremely unsafe, as they assume that the input is a list
    -- of lists each with length three
    root = fst $ head mapping
    mapping :: [(String, (String, String))]
    mapping = fmap (\(p:c1:c2:[]) -> (p, (c1, c2))) list

    -- Recursively build our tree, using the association list defined above to
    -- look up the children for each node. If there are no children, we return
    -- a node with EmptyTree children instead.
    toTree' root = let childs = lookup root mapping
                   in  maybe (Node root EmptyTree EmptyTree)
                             (\(l, r) -> Node root (toTree' l) (toTree' r))
                             childs

此功能可将列表输入转换为[(String, (String, String))],名为mapping。使用lookup的{​​{1}}功能,我们可以使用List作为关联列表,并搜索与父mapping相关联的子(String, String)

然后,我们使用String函数递归构建树。在每个节点上,它执行查找该节点子节点的toTree'关联列表。如果有孩子,它会递归地将它们添加到树中。以这种方式构建树意味着输入元组可以按任何顺序排列。在mapping处使用lookup函数的效率非常低,如果性能受到关注,则可以使用List代替。

打印树

您的方法使用递归,这可能是最简单的方法,但您仍然尝试收集所有输出的列表,然后在最后收集Data.Map。我认为在遍历树时简单地输出节点内容更容易,除非有一些理由不这样做(如果你想收集输出,你可以使用mapM monad而不是Writer。 / p>

我的方法使用IO计数器来跟踪缩进级别:

Int

输出格式很好:

printTree :: Tree String -> IO ()
printTree t = printTree' t 0
  where
    -- if we reached the bottom of the tree, do nothing
    printTree' EmptyTree _ = return ()

    -- We first print the current node's string value, and then recursively
    -- call ourselves for the children. This is a simple depth-first tree
    -- traversal, for which binary trees are well-suited.
    printTree' (Node s l r) depth = do
      putStrLn $ replicate depth ' ' ++ s
      printTree' l (depth + 2)
      printTree' r (depth + 2)

另类

我怀疑这是一个家庭作业问题或者类似问题,它可能会使二叉树的使用变得不可协商,但是这里很容易对邻接列表进行深度优先遍历,而不会将其转换为二叉树(算法看起来非常相似):

Bob
  Dylan
    Cole
      Patrick
      Patricia
    Sarah
      David
      Fiona
  Susan
    Michael
    Madeline

此方法将输入数据集更像广义图。它可以处理有两个以上孩子的父母,并且使用更复杂的输入数据集,我们可以利用图表方法来做更酷的事情。但是,我们的simpleTreePrint :: [[String]] -> IO () simpleTreePrint list = p' (fst $ head mapping) 0 where -- this recursive function prints the 'root' name (eg "Bob") that it is -- called with, then recursively calls itself for all the children of -- that name that it finds in the 'mapping' data structure p' :: String -> Int -> IO () p' root depth = let children = maybe [] id $ lookup root mapping in do putStrLn $ replicate depth ' ' ++ root forM_ children $ \c -> p' c (depth + 2) -- to make child lookups easier, we convert the original list of lists -- of names into tuples whose first values are the 'parent' name, and -- whose second values are the remaining names. This allows us to use the -- regular List lookup function, which is not efficient but may suffice -- for this application mapping :: [(String, [String])] mapping = fmap (\(p:cs) -> (p, cs)) list 函数可能会破坏,因为只有当输入数据严格地是树时它才会真正起作用。

答案 2 :(得分:0)

让我们给一个子父母联想列表:

type Parents = [ [String] ]

theParents :: Parents
theParents = [ ["Bob","Dylan","Susan"], ["Dylan","Cole","Sarah"], ... ]

首先必须编写一个查找此列表中数据的函数:

lookupParents :: Parents -> String -> (Maybe String, Maybe String)
lookupParents pars name = ...???...

e.g:

lookupParents theParents "Bob" = (Just "Dylan", Just "Susan")
lookupParents theParents "nobody" = (Nothing, Nothing)

接下来,您的buildTree功能将如下所示:

buildTree :: Parents -> String -> Tree String
buildTree parents rootName = Node rootName leftTree rightTree
  where (mleft, mright) = lookupParents parents rootName
        leftTree = ... some function of mleft ...
        rightTree = ... some function of mright ...