函数类型为Tree a-> Tree(a,Int)。我想对整个树进行计数,并对每个出现的叶子进行相应编号。
到目前为止,我已经尝试过:
labelTree :: Tree a -> Tree (a, Int)
labelTree (Leaf a) = Leaf (a,1)
labelTree (tr) = labelTree' (tr) 0
labelTree' :: Tree a -> Int -> (Tree (a,Int))
labelTree' (Leaf a) n = Leaf (a,(n+1))
labelTree' (Node l r) n = (labelTree' (r) (snd (labelTree' (l) n)))
问题是我不确定为什么它会给我以下表达式的类型错误:labelTree' (Node l r) n = (labelTree' (r) (snd (labelTree' (l) n)))
请指出我出了错!
答案 0 :(得分:12)
我和chepner有相同的想法:使用State。但是,您不必自己编写递归,因为这是对树的简单遍历!相反,为您的树派出Traversable和Foldable(无论如何都是好主意),然后依靠它们为您进行递归:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
import qualified Control.Monad.Trans.State.Strict as S
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving (Show, Functor, Foldable, Traversable)
labelTree :: Tree a -> Tree (a, Int)
labelTree t = S.evalState (traverse applyLabel t) 0
where applyLabel x = do
n <- S.get
S.modify' succ
pure (x, n)
*Main> labelTree (Node (Node (Leaf 'a') (Leaf 'b')) (Leaf 'c'))
Node (Node (Leaf ('a',0)) (Leaf ('b',1))) (Leaf ('c',2))
此实现的一个不错的功能是,如果您更改树的结构(例如,将数据存储在内部节点中),它将仍然有效。像交换节点顺序这样的错误是不可能的,因为您根本不在该级别上工作:Traverable会为您处理它。
答案 1 :(得分:9)
您可能需要的是某种 accumulator :通过递归调用传递的变量,每次“分配”下一个ID时每次递增的变量。
因此,我们根据辅助函数go
定义了函数。 go
将返回一个2元组:“已标记”树,以及我们将“分派”的下一个ID。由于我们定义了递归调用,因此以后将使用它:
labelTree :: Tree a -> Tree (a, Int)
labelTree = fst . go 0
where go ...
因此go
的类型为Int -> Tree a -> (Int, Tree (a, Int))
。如果我们看到一个Leaf
,那么我们就“派遣”该id,然后将该叶子连同n + 1
作为元组的第二部分一起返回,例如:
go (Leaf x) n = (Leaf (x, n), n+1)
对于一个节点,我们首先将ID分配到左侧的子树,然后以该元组的第二项作为开始将元素分配到右侧的子树,例如:
go (Node l r) n0 = (Node ll lr, n2)
where (ll, n1) = go l n0
(lr, n2) = go r n1
因此,我们首先调用go l n0
来标记左子树,并获得2元组(ll, n1)
,其中包含ll
标记的左子树,以及n1
新的数字稍后派遣。我们调用go r n1
,因此我们将数字分配到以n1
开始的正确子树中。因此,我们的go
函数将返回带有标签子树的新Node
和要分发的新编号。这对于此函数的调用者很重要。
因此,我们可以用以下方式标记一棵树:
labelTree :: Tree a -> Tree (a, Int)
labelTree = fst . go 0
where go (Leaf x) n = (Leaf (x, n), n+1)
go (Node l r) n0 = (Node ll lr, n2)
where (ll, n1) = go l n0
(lr, n2) = go r n1
答案 2 :(得分:5)
您可以使用State
单子来跟踪要添加到节点上的数字。
labelTree :: Tree a -> Tree (a, Int)
labelTree l = evalState (labelTree' l) 0
where labelTree' :: Tree a -> State Int (Tree (a, Int))
labelTree' (Node l r) = Node <$> labelTree' l <*> labelTree' r
labelTree' (Leaf a) = do n <- get
put $ n + 1
return $ Leaf (a, n)
labelTree'
建立了一个有状态的计算,该计算将按有序遍历对叶子进行编号。 evalState
然后在初始状态为0的情况下运行计算,以使叶子从0开始编号。
递归情况看起来很像普通的树函数。您可以使用Node
实例,而不是简单地将Applicative
应用于每个递归调用的结果。
基本案例使用当前状态为每个Leaf
编号,并更新下一个叶子的状态。
(请注意,这与Willem Van Onsem's answer非常相似。鉴于State s a
实际上是类型s -> (a, s)
的函数的包装,可以将labelTree' :: Tree a -> State Int (Tree (a, Int), Int)
的类型哄骗为与go
相同的类型:
labelTree' :: Tree a -> State Int (Tree (a, Int))
~ Tree a -> Int -> (Tree (a, Int), Int)
go :: Tree a -> Int -> (Tree (a, Int), Int)
)
答案 3 :(得分:3)
这是又快又脏的版本:
{-# language DeriveTraversable #-}
import Data.Traversable (mapAccumL)
data Tree a
= Leaf a
| Node (Tree a) (Tree a)
deriving (Functor, Foldable, Traversable)
labelTree :: Tree a -> Tree (a, Int)
labelTree = snd .
mapAccumL (\k a -> (k+1, (a, k))) 1
不幸的是,这可能有点太懒了以至于总体上效率不高。我仍在尝试找出如何在这里打出懒惰的甜蜜点。