[英]Number all occurring leaves in a tree from left to right in Haskell
函數類型是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 lr) n = (labelTree' (r) (snd (labelTree' (l) n)))
請說明我出錯的地方!
我和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))
此實現的一個很好的特性是,如果更改樹的結構(例如,將數據存儲在內部節點中),它仍然可以工作。 不可能像交換節點的順序那樣犯錯,因為你根本不在那個級別工作:Traversable為你處理它。
你可能需要的是某種累加器 :一個你通過遞歸調用的變量,每次你在“分配”下一個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
來標記左子樹,並獲得包含ll
標記的左子樹的2元組(ll, n1)
,以及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
您可以使用State
monad跟蹤要添加到節點的數字。
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開始編號。
遞歸情況看起來很像普通的樹函數。 您可以使用Applicative
實例,而不是簡單地將Node
應用於每個遞歸調用的結果。
基本案例使用當前狀態為每個Leaf
編號,並更新下一個葉子的狀態。
(請注意,這與Willem Van Onsem的答案非常相似。鑒於State sa
實際上是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)
)
這是快速而骯臟的版本:
{-# 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
不幸的是,這可能有點太懶了,總的來說效率不高。 我還在努力弄清楚如何在這里找到懶惰的甜蜜點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.