“森林砍伐”如何从程序中删除“树”?


12

我想了解森林砍伐如何消耗,并产生在同一时间列表(由折叠和展开功能- 看到代码审查这个很好的答案在这里),但是当我相比,随着对技术的维基百科条目也谈到了“删除程序中的树木。

我了解如何将程序解析为语法分析树(是吗?),但是对某种形式的程序进行简化(是吗?)时使用砍伐森林的含义是什么?我该如何对我的代码进行处理?

Answers:


9

Yatima2975似乎已经涵盖了您的前两个问题,我将尝试涵盖第三个问题。为此,我将处理一个不切实际的简单案例,但是我敢肯定,您将能够想象出更现实的事情。

假设您要计算阶完整二叉树的深度。(未标记的)二叉树的类型为(使用Haskell语法):n

type Tree = Leaf | Node Tree Tree

现在,阶的完整树为:n

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 ħdepth (full n)nfulldepthdepth (full n)full_depth

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

然后,您已经将指数时间过程变成了线性时间过程。。。如果还有其他优化可以识别是整数的同一性,那将是很酷的,但是我不确定在实践中使用了优化。full_depth

唯一执行自动毁林的主流编译器是GHC,如果我没记错的话,这仅在组成内置函数时才会执行(出于技术原因)。


获奖是因为我从这个答案的制定方式中获得的收益比从其他答案中获得的收益更多,即使它们基本上覆盖了相同的领域。
Cris Stringfellow

6

首先,列表是一种树。如果我们将列表表示为链接列表,则它只是一棵树,其每个节点具有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))

我们转换ListListF通过在其子表递归折叠,然后用定义上的功能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

我将省略细节,希望这种模式是显而易见的。

也可以看看:


好答案,谢谢。链接和详细示例很有价值。
Cris Stringfellow

3

这有点令人困惑,但是应用了砍伐森林(在编译时)以消除将要创建的中间树(在运行时)。砍伐森林不涉及砍掉抽象语法树的一部分(即消除死枝:-)

可能使您失望的另一件事是,列表树,只是非常不平衡的树!


是的。不平衡!
Cris Stringfellow
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.