在Haskell中从左到右对树中所有出现的叶子进行编号

时间:2019-02-28 19:35:31

标签: haskell binary

函数类型为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)))

请指出我出了错!

4 个答案:

答案 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

不幸的是,这可能有点太懒了以至于总体上效率不高。我仍在尝试找出如何在这里打出懒惰的甜蜜点。