我想了解森林砍伐如何消耗,并产生在同一时间列表(由折叠和展开功能- 看到代码审查这个很好的答案在这里),但是当我相比,随着对技术的维基百科条目也谈到了“删除程序中的树木。
我了解如何将程序解析为语法分析树(是吗?),但是对某种形式的程序进行简化(是吗?)时使用砍伐森林的含义是什么?我该如何对我的代码进行处理?
我想了解森林砍伐如何消耗,并产生在同一时间列表(由折叠和展开功能- 看到代码审查这个很好的答案在这里),但是当我相比,随着对技术的维基百科条目也谈到了“删除程序中的树木。
我了解如何将程序解析为语法分析树(是吗?),但是对某种形式的程序进行简化(是吗?)时使用砍伐森林的含义是什么?我该如何对我的代码进行处理?
Answers:
Yatima2975似乎已经涵盖了您的前两个问题,我将尝试涵盖第三个问题。为此,我将处理一个不切实际的简单案例,但是我敢肯定,您将能够想象出更现实的事情。
假设您要计算阶完整二叉树的深度。(未标记的)二叉树的类型为(使用Haskell语法):
type Tree = Leaf | Node Tree Tree
现在,阶的完整树为:
full : Int -> Tree
full n | n == 0 = Leaf
full n = Node (full (n-1)) (full (n-1))
一棵树的深度是由
depth : Tree -> Int
depth Leaf = 0
depth (Node t1 t2) = 1 + max (depth t1) (depth t2)
现在,您可以看到任何计算都将首先使用构造阶完整树,然后使用解构该树。砍伐森林依赖于观察,这样的图案(结构接着解构)通常可以短路:我们可以更换到任何呼叫由单个呼叫:ñ ˚F ü 升升d Ë p 吨ħ d Ë p 吨ħ(˚F û 升升 Ñ )˚F ü 升升_ d Ë p 吨ħ
full_depth : Int -> Int
full_depth n | n == 0 = 0
full_depth n = 1 + max (full_depth (n-1)) (full_depth (n-1))
这避免了全树的内存分配,并且避免了执行模式匹配的需要,从而大大提高了性能。此外,如果您添加优化
max t t --> t
然后,您已经将指数时间过程变成了线性时间过程。。。如果还有其他优化可以识别是整数的同一性,那将是很酷的,但是我不确定在实践中使用了优化。
唯一执行自动毁林的主流编译器是GHC,如果我没记错的话,这仅在组成内置函数时才会执行(出于技术原因)。
首先,列表是一种树。如果我们将列表表示为链接列表,则它只是一棵树,其每个节点具有1个或0个后代。
解析树只是将树用作数据结构。树在计算机科学中有许多应用,包括排序,实现地图,关联数组等。
通常,列表,树等是递归数据结构:每个节点包含一些信息以及同一数据结构的另一个实例。折叠是对所有此类结构的操作,该操作将节点递归地转换为“自下而上”的值。展开是相反的过程,它将值转换为“自上而下”的节点。
对于给定的数据结构,我们可以机械地构造它们的折叠和展开功能。
举个例子,让我们看一下列表。(在输入示例时,我将使用Haskell作为示例,并且其语法非常简洁。)List是一个结束符或一个值和一个“尾部”。
data List a = Nil | Cons a (List a)
现在,让我们想象一下我们正在折叠一个列表。在每一步中,我们都将折叠当前节点,并且已经折叠了它的递归子节点。我们可以将这种状态表示为
data ListF a r = NilF | ConsF a r
其中,r
是通过折叠子列表构造的中间值。这使我们可以在列表上表达折叠功能:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
我们转换List
成ListF
通过在其子表递归折叠,然后用定义上的功能ListF
。如果您考虑一下,这只是standard的另一种表示形式foldr
:
foldr :: (a -> r -> r) -> r -> List a -> r
foldr f z = foldList g
where
g NilF = z
g (ConsF x r) = f x r
我们可以unfoldList
用相同的方式构造:
unfoldList :: (r -> ListF a r) -> r -> List a
unfoldList f r = case f r of
NilF -> Nil
ConsF x r' -> Cons x (unfoldList f r')
同样,它只是的另一种表示形式unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(注意Maybe (a, r)
与相同)ListF a r
。
我们也可以构造一个森林砍伐函数:
deforest :: (ListF a r -> r) -> (s -> ListF a s) -> s -> r
deforest f u s = f (map (deforest f u) (u s))
where
map h NilF = NilF
map h (ConsF x r) = ConsF x (h r)
它只是省去了中间体List
,并将折叠和展开功能融合在一起。
相同的过程可以应用于任何递归数据结构。例如,一棵树的节点可以有0、1、2或后代,其值在1或0分支节点上:
data Tree a = Bin (Tree a) (Tree a) | Un a (Tree a) | Leaf a
data TreeF a r = BinF r r | UnF a r | LeafF a
treeFold :: (TreeF a r -> r) -> Tree a -> r
treeFold f (Leaf x) = f (LeafF x)
treeFold f (Un x r) = f (UnF x (treeFold f r))
treeFold f (Bin r1 r2) = f (BinF (treeFold f r1) (treeFold f r2))
treeUnfold :: (r -> TreeF a r) -> r -> Tree a
treeUnfold f r = case f r of
LeafF x -> Leaf x
UnF x r -> Un x (treeUnfold f r)
BinF r1 r2 -> Bin (treeUnfold f r1) (treeUnfold f r2)
当然,我们可以deforestTree
像以前一样机械地进行创建。
(通常,我们treeFold
更方便地表示为:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
我将省略细节,希望这种模式是显而易见的。
也可以看看:
这有点令人困惑,但是应用了砍伐森林(在编译时)以消除将要创建的中间树(在运行时)。砍伐森林不涉及砍掉抽象语法树的一部分(即消除死枝:-)
可能使您失望的另一件事是,列表是树,只是非常不平衡的树!