簡體   English   中英

在Haskell中從左到右對樹中所有發生的葉子進行編號

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM