BST:如何根据变形折叠定义“插入”? 拟态

问题描述

我有一个典型的二叉搜索树数据类型:

data Tree a
  = Empty
  | Branch a (Tree a) (Tree a) deriving Show

和变形术

foldt :: b -> (a -> b -> b -> b) -> Tree a -> b
foldt empty _ Empty = empty
foldt empty branch (Branch a l r) = branch a (foldt empty branch l) (foldt empty branch r)

我尝试使用 foldt 定义一个插入函数并得到了一些有趣的结果:

insert :: (Ord a) => a -> Tree a -> Tree a
insert x = foldt (single x) insertb
  where insertb a left right
          | x == a = Branch x left right
          | x < a = Branch a (insert x left) right
          | x > a = Branch a left (insert x right)
ghci> mytree = insert 2 (Branch 3 Empty Empty)  
ghci> mytree
Branch 3 (Branch 2 (Branch 2 Empty Empty) (Branch 2 Empty Empty)) (Branch 2 Empty Empty)
ghci> 

当然,传统的插入方法的行为符合预期:

insert' :: (Ord a) => a -> Tree a -> Tree a
insert' x Empty = single x
insert' x (Branch a left right)
  | x == a = Branch x left right
  | x < a = Branch a (insert' x left) right
  | x > a = Branch a left (insert' x right)
ghci> mytree2 = insert' 2 (Branch 3 Empty Empty)
ghci> mytree2
Branch 3 (Branch 2 Empty Empty) Empty
ghci>

有没有办法根据 insert 来定义 foldt,还是我在这里挑错了树(ha)?

解决方法

让我们定义一个函数

insertMaybe :: Ord a => Tree a -> Maybe a -> Tree a

这个函数需要一棵树,也可能是一个元素。在 Just 情况下,插入元素。在 Nothing 情况下,树返回不变。那么我们可以定义

insert a t = insertMaybe t (Just a)

现在:

insertMaybe :: Ord a => Tree a -> Maybe a -> Tree a
insertMaybe = foldt leaf branch
  where
    leaf (Just new) = ?
    leaf Nothing = ?

    branch a l r Nothing = ?
    branch a l r (Just new)
      | ... = ?
      ...

或者:

data Ins a = Ins
  { inserted :: Tree a,notInserted :: Tree a }

insert a t = inserted (insertAndNot a t)

-- Return the tree with the 
-- element inserted,and also unchanged.
insertAndNot :: Ord a => a -> Tree a -> Ins a
insertAndNot new = foldt leaf branch
  where
    leaf = Ins ? ?
    branch a ~(Ins li lni) ~(Ins ri rni)
      | ... = Ins ? ?
      ...

拟态

上述解决方案有一个主要的效率问题:他们完全重建树结构只是为了插入一个元素。正如 amalloy 所建议的,我们可以通过将 foldt(变形)替换为 parat(变形)来解决这个问题。 parat 允许 branch 函数访问递归修改和未修改的子树。

parat :: b -> (a -> (Tree a,b) -> (Tree a,b) -> b) -> Tree a -> b
parat leaf _branch Empty = leaf
parat leaf branch (Branch a l r) =
  branch a
         (l,parat leaf branch l)
         (r,parat leaf branch r)

方便的是,使用 insert 定义 parat 也稍微更容易。你能看出来吗?这最终成为我建议使用 foldt 的“替代”方式的有效版本。

,

感谢 dfeuer 和 amalloy 提供的关于拟态的技巧,TIL!

给定 Tree 数据类型的拟态:

parat :: b -> (a -> (Tree a,b) -> b) -> Tree a -> b
parat empty _ Empty = empty
parat empty branch (Branch a l r) =
  branch a
         (l,parat leaf branch r)

我们可以写一个插入函数:

insert :: Ord a => a -> Tree a -> Tree a
insert x = parat (single x) branch
  where branch a (l,l') (r,r')
          | x == a = Branch x l r
          | x < a = Branch a l' r
          | x > a = Branch a l r'
ghci> mytree = insert 2 (Branch 3 Empty Empty)
ghci> mytree
Branch 3 (Branch 2 Empty Empty) Empty
ghci>

测试更大的树...

import Data.Function

mytree :: Tree Integer
mytree = (Branch 3 Empty Empty) & insert 2 & insert 4 & insert 6 & insert 5 & insert 10

inorder :: Tree a -> [a]
inorder = foldt [] (\a l r -> l ++ [a] ++ r)
ghci> mytree
Branch 3 (Branch 2 Empty Empty) (Branch 4 Empty (Branch 6 (Branch 5 Empty Empty) (Branch 10 Empty Empty)))
ghci> inorder mytree
[2,3,4,5,6,10]
ghci>

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...