Algorithm 如何在Haskell中编写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

我需要遍历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))) ("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))