假设我们已经存在树状数据,我们想要添加有关每个节点深度的信息。我们怎样才能轻松实现这一目标呢?
Data Tree = Node Tree Tree | Leaf
对于每个节点,我们希望以恒定的复杂度了解它的深度。我们有来自外部模块的数据,因此我们有如上所示的信息。现实生活中的例子是外部HTML解析器,它只提供XML树,我们想收集数据,例如每个节点包含多少个超链接。
为遍历树和收集数据创建了功能语言,应该有一个简单的解决方案。
明显的解决方案是创建并行结构。我们可以做得更好吗?
答案 0 :(得分:4)
我从Chris Okasaki的精彩Purely Functional Data Structures中学到的标准技巧是缓存每个节点上昂贵操作的结果。 (也许这个技巧在Okasaki的论文之前就已经知道了;我不知道。)你可以提供智能构造函数来为你管理这些信息,这样构建树就不必痛苦了。例如,当昂贵的操作是深度时,您可以写:
module SizedTree (SizedTree, sizedTree, node, leaf, depth) where
data SizedTree = Node !Int SizedTree SizedTree | Leaf
node l r = Node (max (depth l) (depth r) + 1) l r
leaf = Leaf
depth (Node d _ _) = d
depth Leaf = 0
-- since we don't expose the constructors, we should
-- provide a replacement for pattern matching
sizedTree f v (Node _ l r) = f l r
sizedTree f v Leaf = v
构造SizedTree
在每个节点上花费O(1)额外工作(因此将n节点Tree
转换为SizedTree
是O(n)工作,但是回报是检查SizedTree
- 或任何子树的深度 - 是O(1)操作。
答案 1 :(得分:2)
您确实需要一些可以存储这些Int
的数据。将Tree
定义为
data Tree a = Node Tree a Tree | Leaf a
然后编写一个函数
annDepth :: Tree a -> Tree (Int, a)
您的原始Tree
是Tree ()
并且使用模式同义词可以恢复很好的构造函数。
如果由于某种原因想保留原始树,可以定义视图:
{-# LANGUAGE GADTs, DataKinds #-}
data Shape = SNode Shape Shape | SLeaf
data Tree a sh where
Leaf :: a -> Tree a SLeaf
Node :: Tree a lsh -> a -> Tree a rsh -> Tree a (SNode lsh rsh)
通过这种方式,您可以保证带注释的树具有与未注释的形状相同的形状。但如果没有适当的依赖类型,这种方法并不好。
答案 2 :(得分:0)
标准解决方案是@DanielWagner建议的,只是扩展数据结构。这可能有点不方便,但可以解决:用于创建实例和使用records for pattern matching的智能构造函数。
也许Data types a la carte可以提供帮助,尽管我自己并没有使用过这种方法。基于此的库compdata。
完全不同的方法是有效地记住您需要的值。我试图解决similar problem,其中一个解决方案由库stable-memo提供。请注意,这不是一种纯粹的功能方法,因为库内部基于对象标识,但接口是纯粹的,并且完美地用于此目的。