我有树数据类型:
data Tree a b = Branch b (Tree a b) (Tree a b) | Leaf a
...我需要将其设为Show
的实例,而不使用deriving
。我发现用两片树叶很好地显示一个小分支很容易:
instance (Show a, Show b) => Show (Tree a b) where
show (Leaf x) = show x
show (Branch val l r) = " " ++ show val ++ "\n" ++ show l ++ " " ++ show r
但是如何将一个漂亮的结构扩展到任意大小的树?似乎确定间距需要我知道底部有多少叶子(或者总共有多少叶子),以便我可以分配我需要的所有空间并且只需要工作。 “我可能需要调用一个大小函数。我可以看到这是可行的,但这是否使它变得更难?
答案 0 :(得分:14)
您可以在基础drawTree
模块中学习Data.Tree
函数。只要无耻地导入它就会给你这样的东西:
import Data.Tree hiding (Tree )
data Tree a b = Branch b (Tree a b) (Tree a b)
| Leaf a deriving (Eq,Ord,Show)
toDataTree (Leaf a) = Node a []
toDataTree (Branch b cs ds) = Node b [toDataTree cs, toDataTree ds]
d = Branch "1" (Branch "11" (Leaf "111") (Leaf "112"))
(Branch "12" (Leaf "121") (Leaf "122"))
e = toDataTree d
f = putStrLn $ drawTree e
{-
*Main> f
1
|
+- 11
| |
| +- 111
| |
| `- 112
|
`- 12
|
+- 121
|
`- 122
-}
答案 1 :(得分:8)
使用applicative的Data.Tree
来源链接,我想出了这个。我想写自己的,所以我可以了解更多。源中的drawTree
方法被推广为与具有多个子节点的节点一起使用;我的只是二元树。
注意:我的树定义与OP略有不同。我不太明白a
类型参数的用途,但方法应该是相同的
data Tree a
= Branch (Tree a) a (Tree a)
| Leaf
prettyprint (Leaf)
= "Empty root."
-- unlines concats a list with newlines
prettyprint (Branch left node right) = unlines (prettyprint_helper (Branch left node right n h))
prettyprint_helper (Branch left node right)
= (show node) : (prettyprint_subtree left right)
where
prettyprint_subtree left right =
((pad "+- " "| ") (prettyprint_helper right))
++ ((pad "`- " " ") (prettyprint_helper left))
pad first rest = zipWith (++) (first : repeat rest)
prettyprint_helper (Leaf)
= []
产生类似
的树4
+- 8
| +- 9
| | +- 10
| `- 6
| +- 7
| `- 5
`- 2
+- 3
`- 1
我只是想解释一下pad
函数是如何工作的,因为这对我来说是最难的(在源代码中称为shift
)。
首先,zipWith
应用一个函数(第一个参数)来“加入”两个列表。 zipWith (+) [1, 2, 3] [4, 5, 6]
返回[5, 7, 9]
。当其中一个列表为空时它会停止。 zipWith
仅应用于一个列表会返回一个可应用于压缩第二个列表的函数(我相信这称为function currying)。这是pad
函数的简单版本:
> let pad = zipWith (++) (repeat " ")
> :type pad
pad :: [[Char]] -> [[Char]]
> pad ["1", "2", "3"]
[" 1", " 2", " 3"]
注意:
1.其中一个列表是无限的(repeat " "
),但当其中一个列表为空时,它会停止
2. zipWith
只接受一个函数和一个列表。 pad
是一个函数,它获取字符/字符串列表的列表,并返回字符/字符串列表的压缩列表。因此,您可以在单个列表中应用pad
以使用第一个列表将其压缩
现在让我们来看看
prettyprint_subtree left right =
((pad "+- " "| ") (prettyprint_helper left))
++ ((pad "`- " " ") (prettyprint_helper right))
(pad "+- " "| ")
创建了一个无限列表,如["+- ", "| ", "| ", "| ", ...]
。 (prettyprint_helper right)
构建表示右侧子树的行列表,从右侧的根节点开始。但整棵树需要向右移动;我们通过用填充物压缩它来做到这一点。我们使用无限列表,因为我们不知道子树有多大;总是有足够的"| "
来填充额外的行(这也因为懒惰的评估而起作用)。注意第一行;即子树根节点,用"+- "
填充,而右边节点用“符号”填充。
左侧实际上是相同的。左节点的表示法是"`- "
。唯一的区别是填充; " "
代替"| "
。那么为什么左边的节点不需要“分支”呢?好吧,您可以将其视为背后正确的节点(附加填充;左侧)到达下面的左侧节点。您需要在右侧后面的填充将左节点/子树连接到父节点。除了父树之外,树的左侧后面没有任何东西。这让我想到了最后一点;每个子树,在prettyprint_helper
函数中表示为一个行列表,每个父树都会获得一个额外的填充级别。我认为最好以一个例子说明。
在创建上面的树时(注意,我不完全知道执行顺序,特别是对于懒惰的评估,但这只是为了帮助可视化它的工作原理):
假设我们递归到10
。那么左边的子树和右边的子树是空的,所以prettyprint_helper (Branch Leaf 10 Leaf)
会返回["10"]
。
现在我们达到9
。它的子树是:"9" : ([] ++ ((pad "+- " "| ") [10]))
(没有左侧),或"9" : ["+- 10"]
,或者:
9
+- 10
现在我们达到8
。 ((pad "+- " "| ") (prettyprint_helper right))
创建:
+- 9
| +- 10
您可以自己追踪,但左侧是:
6
+- 7
`- 5
哪个打击垫(第一个元素"`- "
,休息" "
):
`- 6
+- 7
`- 5
所以总共为8,左边附加在右侧,我们有:
8
+- 9
| +- 10
`- 6
+- 7
`- 5
如果我们前进一步,则为8
树填充此4
子树,并再次通过另一侧跟踪以验证其是否有效。你得到了
+- 8
| +- 9
| | +- 10
| `- 6
| +- 7
| `- 5
最终结果如上。请记住,在整个过程中,树被表示为行列表。只有在最后才与unlines
放在一起。也许我的绘图是误导性的,因为它可能看起来像是以多行字符串传递子树。一旦理解了这一点,就可以很容易地在左侧和右侧节点之间添加额外的分支("|"
),就像在Data.Tree
的{{1}}函数中一样。我会让你弄清楚:))
如果这太过分,我道歉;作为初学者,我很难从源头上理解,这对我来说是个大跳跃。我希望它能帮助其他人试图解决这个问题。
答案 2 :(得分:1)
另一种实现:
───"a"
└──"b"
└──"c"
| ┌──"g"
| ┌──"c"
| | └──"f"
| ┌──"a"
| | | ┌──"e"
| | └──"b"
| | └──"d"
| ┌──"x"
| | | ┌──"g"
| | | ┌──"c"
| | | | └──"f"
| | └──"a"
| | | ┌──"e"
| | └──"b"
| | └──"d"
| ┌──"f"
└──"d"
import Data.List
data Btree a = Empty | Node a (Btree a) (Btree a) deriving Show
tr_l = Node "b" (Node "d" Empty Empty) (Node "e" Empty Empty)
tr_r = Node "c" (Node "f" Empty Empty) (Node "g" Empty Empty)
tr = Node "a" tr_l tr_r :: Btree String
tx = Node "x" tr tr
trr = Node "a" (Node "b" (Node "c" (Node "d" Empty (Node "f" Empty tx)) Empty) Empty) Empty:: Btree String
data ParentDir = PLeft | PRight | NoParent deriving (Show,Eq)
type ParentPos = Int
type Level = Int
dline = '|'
factor = 4
m c1 c2 = if c1 == dline then c1 else c2
zipWith' f xs [] = xs
zipWith' f [] xs = xs
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
build_line pd a pp level = foldl (zipWith' m) "" (((++"|").(flip replicate ' ') <$> (factor*) <$> pp)++[(replicate (factor*level) ' ')++cn++show a])
where cn = case pd of PLeft -> "└──"
PRight -> "┌──"
NoParent -> "───"
tprint :: Show a => ParentDir -> [ParentPos] -> Level -> Btree a -> [String]
tprint _ _ _ Empty = []
tprint pd pp level (Node a l r) = tprint PRight new_pp_r (level+1) r ++
[build_line pd a pp level] ++
tprint PLeft new_pp_l (level+1) l
where new_pp_r = case pd of PRight -> pp
PLeft -> pp++[level]
NoParent -> pp
new_pp_l = case pd of PRight -> pp++[level]
PLeft -> pp
NoParent -> pp
printt t = putStr $ (intercalate "\r\n" (tprint NoParent [] 0 t))++"\r\n"