Algorithm 如何在Haskell中编写N元树遍历函数
我需要遍历N元树,并在我按预定顺序访问时向每个节点添加编号。我有如下定义的n元树:Algorithm 如何在Haskell中编写N元树遍历函数,algorithm,haskell,recursion,tree,functional-programming,Algorithm,Haskell,Recursion,Tree,Functional Programming,我需要遍历N元树,并在我按预定顺序访问时向每个节点添加编号。我有如下定义的n元树: data NT a = N a [NT a] deriving Show 示例: 如果我有以下树: let ntree = N "eric" [N "lea" [N "kristy" [],N "pedro" [] ,N "rafael" []],N "anna" [],N "bety" []] N (N (Leaf ("anna",2)) ("leo",1) (Leaf ("laura",3))) ("er
data NT a = N a [NT a] deriving Show
示例:
如果我有以下树:
let ntree = N "eric" [N "lea" [N "kristy" [],N "pedro" [] ,N "rafael" []],N "anna" [],N "bety" []]
N (N (Leaf ("anna",2)) ("leo",1) (Leaf ("laura",3))) ("eric",0) (N (Leaf ("john",5)) ("joe",4) (Leaf ("eddie",6)))
我想把它转换成
let ntree = N (1,"eric") [N (2,"lea") [N (3,"kristy") [],N (4,"pedro") [] ,N (5,"rafael") []],N (6,"anna") [],N (7,"bety") []]
“预定”并没有那么重要
我想看看如何编写一个函数,在不同级别之间传递值,比如如何将数字传递给后续列表,以及如何将更新后的数字传递给父级,并将该数字传递给其他分支
到目前为止,我已经能够编写如下函数:
traverse :: NT String -> String
traverse (N val []) =" "++val++" "
traverse (N val list) =val++" " ++ (concat $ map traverse list)
哪个输出
"eric lea kristy pedro rafael anna bety "
编辑:问题是:
如何编写函数
numberNodes :: NT a -> NT (a,Int)
根据树的前序遍历对节点进行编号
对我来说,最难理解的是传递辅助数据,你能详细说明一下吗
在这个具体的例子中,它是一个Int,表示“时间”或我遍历这棵树的顺序。我将在取得一些进展后立即更新这个答案 现在我把问题从n元树简化为二叉树
data T a = Leaf a | N (T a) a (T a) deriving Show
numberNodes:: T a -> T (a,Int)
numberNodes tree = snd $ numberNodes2 tree 0
numberNodes2:: T a -> Int -> (Int, T (a,Int))
numberNodes2 (Leaf a) time = (time,Leaf (a,time))
numberNodes2 (N left nodeVal right) time = (rightTime, N leftTree (nodeVal,time) rightTree )
where (leftTime,leftTree) = numberNodes2 left (time+1)
(rightTime,rightTree) = numberNodes2 right (leftTime+1)
函数numberNodes从此树创建:
let bt = N (N (Leaf "anna" ) "leo" (Leaf "laura")) "eric" (N (Leaf "john") "joe" (Leaf "eddie"))
以下树:
let ntree = N "eric" [N "lea" [N "kristy" [],N "pedro" [] ,N "rafael" []],N "anna" [],N "bety" []]
N (N (Leaf ("anna",2)) ("leo",1) (Leaf ("laura",3))) ("eric",0) (N (Leaf ("john",5)) ("joe",4) (Leaf ("eddie",6)))
现在只需为n元树重写它…(我不知道怎么做,有什么提示吗?第一次尝试:努力工作
对于n元树的情况,有三件事情在进行:为元素编号、为树编号和为树列表编号。分开处理会有帮助。类型优先:
aNumber :: a -- thing to number
-> Int -- number to start from
-> ( (a, Int) -- numbered thing
, Int -- next available number afterwards
)
ntNumber :: NT a -- thing to number
-> Int -- number to start from
-> ( NT (a, Int) -- numbered thing
, Int -- next available number afterwards
)
ntsNumber :: [NT a] -- thing to number
-> Int -- number to start from
-> ( [NT (a, Int)] -- numbered thing
, Int -- next available number afterwards
)
请注意,这三种类型共享相同的模式。当你看到有一个你正在遵循的模式时,显然是巧合,你知道你有机会学习一些东西。但现在让我们继续前进,稍后再学习
为元素编号很容易:将起始编号复制到输出中,并将其后续编号作为下一个可用编号返回
aNumber a i = ((a, i), i + 1)
对于另外两个,模式(这个词又出现了)是
where
子句(抓取输出的两部分)
对于树,顶级拆分为我们提供了两个组件:元素和列表。在where子句中,我们根据这些类型的指示调用适当的编号函数。在每种情况下,“thing”输出告诉我们应该放置什么来代替“thing”输入。同时,我们将这些数字贯穿其中,因此整体的起始编号是第一个组件的起始编号,第一个组件的“下一个”编号是第二个组件的起始编号,第二个组件的“下一个”编号是整体的“下一个”编号
ntNumber (N a ants) i0 = (N ai aints, i2) where
(ai, i1) = aNumber a i0
(aints, i2) = ntsNumber ants i1
对于列表,我们有两种可能性。空列表没有任何组件,因此我们直接返回它,而不使用任何其他数字。一个“cons”有两个组件,我们和以前一样,按照类型的指示使用适当的编号函数
ntsNumber [] i = ([], i)
ntsNumber (ant : ants) i0 = (aint : aints, i2) where
(aint, i1) = ntNumber ant i0
(aints, i2) = ntsNumber ants i1
让我们试一试
> let ntree = N "eric" [N "lea" [N "kristy" [],N "pedro" [] ,N "rafael" []],N "anna" [],N "bety" []]
> ntNumber ntree 0
(N ("eric",0) [N ("lea",1) [N ("kristy",2) [],N ("pedro",3) [],N ("rafael",4) []],N ("anna",5) [],N ("bety",6) []],7)
我们到了。但是我们快乐吗?嗯,我不是。我有一种恼人的感觉,我写了三次几乎相同的类型,两次几乎相同的程序。如果我想对不同组织的数据(例如,二叉树)进行更多的元素编号,我必须再次编写相同的代码。Haskell代码中的重复模式总是错失良机:培养自我批评意识并询问是否有更整洁的方法很重要
第二次尝试:编号和线程
上面我们看到的两种重复模式是
1.类型的相似性,
2.数字线程化方式的相似性
如果你匹配这些类型,看看它们有什么共同点,你会发现它们都是相同的
input -> Int -> (output, Int)
用于不同的输入和输出。让我们为最大的公共组件命名
type Numbering output = Int -> (output, Int)
现在我们的三种类型是
aNumber :: a -> Numbering (a, Int)
ntNumber :: NT a -> Numbering (NT (a, Int))
ntsNumber :: [NT a] -> Numbering [NT (a, Int)]
您经常在Haskell中看到这样的类型:
input -> DoingStuffToGet output
现在,为了处理线程,我们可以构建一些有用的工具来处理和组合编号操作。要了解我们需要哪些工具,请查看在对组件进行编号后如何组合输出。输出的“东西”部分总是通过将一些没有编号的函数(通常是数据构造函数)应用到一些来自编号的“东西”输出来构建的
为了处理这些函数,我们可以构建一个小工具,它看起来非常像我们的[]
案例,不需要实际的编号
steady :: thing -> Numbering thing
steady x i = (x, i)
不要因为类型使它看起来像是只有一个参数而感到不快:记住,编号事物
缩写了一个函数类型,因此其中确实有另一个->
。我们得到
steady [] :: Numbering [a]
steady [] i = ([], i)
就像在ntsNumber
的第一行一样
但是其他的构造函数呢,N
和(:)
?询问ghci
> :t steady N
steady N :: Numbering (a -> [NT a] -> NT a)
> :t steady (:)
steady (:) :: Numbering (a -> [a] -> [a])
我们将函数作为输出进行编号操作,我们希望通过更多的编号操作来生成这些函数的参数,从而生成一个大的整体编号操作,并将这些编号贯穿其中。该过程的一个步骤是将编号生成的函数输入到编号生成的输入。我将其定义为中缀运算符
($$) :: Numbering (a -> b) -> Numbering a -> Numbering b
infixl 2 $$
与显式应用程序运算符的类型比较,$
> :t ($)
($) :: (a -> b) -> a -> b
此$$
运算符是“编号应用程序”。如果我们能做到正确,我们的代码就变得
ntNumber :: NT a -> Numbering (NT (a, Int))
ntNumber (N a ants) i = (steady N $$ aNumber a $$ ntsNumber ants) i
ntsNumber :: [NT a] -> Numbering [NT (a, Int)]
ntsNumber [] i = steady [] i
ntsNumber (ant : ants) i = (steady (:) $$ ntNumber ant $$ ntsNumber ants) i
按原样(目前)使用a个数。该代码只是进行数据重建,将构造函数和组件的编号过程连接在一起。我们最好给出$$
的定义,并确保其线程正确
($$) :: Numbering (a -> b) -> Numbering a -> Numbering b
(fn $$ an) i0 = (f a, i2) where
(f, i1) = fn i0
(a, i2) = an i1
在这里,我们的旧线程模式只完成一次。fn
和an
中的每一个都是一个函数,需要一个起始编号,而整个fn$$sn
是一个函数,它获取起始编号
aNumber :: a -> Numbering a
aNumber a = steady (,) $$ steady a $$ ????
next :: Numbering Int
next i = (i, i + 1)
aNumber a = steady (,) $$ steady a $$ next
aNumber a = steady ((,) a) $$ next
aNumber a = ...... ((,) a) .. next
> :info Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
listNumber :: (a -> Numbering b) -> [a] -> Numbering [b]
listNumber na [] = steady []
listNumber na (a : as) = steady (:) $$ na a $$ listNumber na as
ntsNumber :: [NT a] -> Numbering [NT (a, Int)]
ntsNumber = listNumber ntNumber
ntNumber :: NT a -> Numbering (NT (a, Int))
ntNumber (N a ants) = steady N $$ aNumber a $$ listNumber ntNumber ants
ntNumber' :: (a -> Numbering b) -> NT a -> Numbering (NT b)
ntNumber' na (N a ants) = steady N $$ na a $$ listNumber (ntNumber' na) ants
myTree :: NT [String]
myTree = N ["a", "b", "c"] [N ["d", "e"] [], N ["f"] []]
> ntNumber' (listNumber aNumber) myTree 0
(N [("a",0),("b",1),("c",2)] [N [("d",3),("e",4)] [],N [("f",5)] []],6)
> :t traverse
traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
import Control.Monad.State
> :t evalState
evalState :: State s a -> s -> a
next' :: State Int Int
next' = get <* modify (1+)
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
data NT a = N a [NT a] deriving (Show, Functor, Foldable, Traversable)
evalState (traverse (\ a -> pure ((,) a) <*> get <* modify (1+)) ntree) 0
-- ^ how to process one element ^^^^^^^^^^^^^^^
-- ^ how to process an entire tree of elements ^^^^^^^^^
-- ^ processing your particular tree ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- ^ kicking off the process with a starting number of 0 ^^^^^^^^^^^^^^^^
{-# LANGUAGE DeriveTraversable #-}
import Data.Traversable
import Data.Tuple
-- original data type from the question
data NT a = N a [NT a]
deriving (Show, Functor, Foldable, Traversable)
-- additional type from @pigworker's answer
type Numbering output = Int -> (output, Int)
-- compare this to signature of ntNumber
-- swap added to match the signature
ntNumberSimple :: (NT a) -> Numbering (NT (a, Int))
ntNumberSimple t n = swap $ mapAccumL func n t
where
func i x = (i+1, (x, i))