使用fold在二叉树上实现映射

时间:2019-02-16 20:24:29

标签: haskell binary-tree fold

我正在努力使用foldBT在树上定义地图。我的想法是将树转换为列表,将运算符映射到列表,然后将列表转换回树。但这听起来效率低下,而且也没有使用foldBT ...我试图运行foldBT (*2) Nil (numTree [3,5,7],但是ghci报告了错误。我不太了解功能foldBt是如何工作的。一个例子会很好。

data SimpleBT a = Nil | N a (SimpleBT a) (SimpleBT a) deriving (Show, Eq)

foldBT :: (a -> b -> b -> b) -> b -> SimpleBT a -> b
foldBT f e Nil = e
foldBT f e (N a left right) = f a (foldBT f e left) (foldBT f e right)

mapTree :: (a -> b) -> SimpleBT a -> SimpleBT b
mapTree f Nil = Nil
mapTree f (N a left right) = N (f a) (mapTree f left) (mapTree f right)

size :: SimpleBT a -> Int
size Nil = 0
size (N _ left right) = 1 + size left + size right

insert ::  SimpleBT a -> a -> SimpleBT a
insert Nil a = N a Nil Nil
insert (N x left right) a
  | size left <= size right = N x (insert left a) right
  | otherwise = N x left (insert right a)

numTree :: [a] -> SimpleBT a
numTree xs = foldl insert Nil xs

2 个答案:

答案 0 :(得分:6)

让我们假设mapTree f = foldBT g e,并求解gefoldBT的定义是:

foldBT g e Nil = e

同时,根据mapTree的定义,我们得到:

mapTree f Nil = Nil

根据mapTree f = foldBT g e的假设,我们可以变换第二个方程并将其贴在第一个方程的旁边:

foldBT g e Nil = Nil
foldBT g e Nil = e

所以e = Nil

让我们现在执行其他子句。 foldBT的定义是:

foldBT g e (N a left right) = g a (foldBT g e left) (foldBT g e right)

与此同时,mapTree的定义是:

mapTree f (N a left right) = N (f a) (mapTree f left) (mapTree f right)

再次,使用我们的假设mapTree f = foldBT g e,我们现在可以重写此等式,并将其粘贴在第一个等式旁边:

foldBT g e (N a left right) = N (f a) (foldBT g e left) (foldBT g e right)
foldBT g e (N a left right) = g a (foldBT g e left) (foldBT g e right)

因此g a = N (f a)将验证此等式。将这些全部写下来,我们得到:

mapTree f = foldBT g e where
    e = Nil
    g a = N (f a)

如果愿意,可以内联eg的定义;我会的。

mapTree f = foldBT (N . f) Nil

答案 1 :(得分:1)

foldBT函数的想法是,它为您数据类型的每个构造函数采用一个参数(加上要折叠的整个SimpleBT a本身的构造函数)。类型为a -> b -> b -> b的第一个对应于递归N构造函数,并显示如何将节点上的值与两个子树的折叠结果组合为整个折叠结果。第二个参数对应于Nil构造函数,并且由于该构造函数不接受任何参数,因此fold的相应参数只是一个常量。

完全折叠列表即可。列表类型[a]也有2个构造函数。其中一个使用a和一个列表,并将元素添加到列表的最前面:a -> [a] -> [a],这将产生类型为a -> b -> b的“折叠函数”。与您的情况一样,另一个构造函数为null(空列表),因此再次对应于类型仅为b的参数。因此,列表的foldr的类型为(a -> b -> b) -> b -> [a] -> b

在Haskell Wikibook的这一章中,对此进行了详尽的讨论,并提供了更多示例:https://en.wikibooks.org/wiki/Haskell/Other_data_structures

关于如何从折叠中构建地图,对于您的特定树类型-考虑到我上面已经说过的,您需要(给定映射函数f :: a -> a0),我们需要考虑一下这对Nil树和对具有叶子和2个分支的树都具有递归作用。另外,由于我们的返回类型当然是同一类型的另一棵树,因此b在这里将是SimpleBT a0

对于Nil,显然我们希望map保持不变,因此foldBT的第二个参数将是Nil。对于其他构造函数,我们希望map将基函数应用于叶子上的值,然后递归映射到2个分支上。这将导致我们转到函数\a left right -> N (f a) (mapTree f left) (mapTree f right)

所以我们可以得出结论,可以按以下方式定义map函数(感谢@DanielWagner和@WillemVanOnsen帮助我修复了第一个损坏的版本):

mapTree :: (a -> b) -> SimpleBT a -> SimpleBT b
mapTree f = foldBT foldFunc Nil
    where foldFunc a l r = N (f a) l r