使用应用程序的链接到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) 构建代表右侧子树的行列表,从右侧的根节点开始。但是整棵树需要向右移动;我们通过用填充物压缩它来做到这一点。我们使用无限列表是因为我们不知道子树有多大;总会有足够的"| "s 来填充额外的行(这也适用于惰性评估)。注意第一行;即子树根节点,用 "+- " 填充,而不是右节点的“符号”。
左侧几乎相同。左节点的符号是"`- "。唯一的其他区别是填充。 " " 而不是 "| "。那么为什么左节点不需要“分支”呢?好吧,您可以将其视为在右侧节点(附加了填充;在左侧)到下面的左侧节点。您需要右侧后面的填充将左侧节点/子树连接到父节点。树的左侧后面没有任何东西,可能除了父树。这使我想到了最后一点;每个子树(表示为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 的drawTree 函数中一样。我会让你弄清楚:)
如果这太过分了,我深表歉意;作为初学者,我很难从源头上理解,这对我来说是一个很大的飞跃。我希望它可以帮助其他试图解决这个问题的人。