在可穿越结构中寻找线性路径

时间:2014-05-20 17:19:25

标签: haskell tree traversal

给出一系列步骤:

>>> let path = ["item1", "item2", "item3", "item4", "item5"]

标有Tree

>>> import Data.Tree
>>> let tree = Node "item1" [Node "itemA" [], Node "item2" [Node "item3" []]]

我希望功能能够完成path中与tree中的标签匹配的步骤,直到它无法再进一步,因为没有更多标签符合这些步骤。具体地说,这是落入" item4" (对于我的用例,我仍然需要指定最后匹配的步骤):

>>> trav path tree
["item3", "item4", "item5"]

如果我允许[String] -> Tree String -> [String]作为trav的类型,我可以编写一个递归函数,同时在两个结构中同步,直到没有与该步骤匹配的标签。但我想知道是否可以使用更通用的类型,特别是Tree。例如:Foldable t => [String] -> t String -> [String]。如果可以,trav如何实施?

我怀疑可能有办法使用lens

2 个答案:

答案 0 :(得分:2)

首先,请让我们使用type Label = String。字符串不完全是描述性的,最后可能不是理想的...

现在。要使用Traversable,您需要选择一个合适的Applicative,其中包含您决定在其“结构”中执行操作所需的信息。您只需在匹配失败后传回信息。这听起来像是Either

因此,猜测将是Either [Label] (t Label)作为预结果。这意味着,我们使用实例化

    traverse :: Traversable t
      => (Label -> Either [Label] Label) -> t Label -> Either [Label] (t Label)

那么我们可以作为参数函数传递什么?

travPt0 :: [Label] -> Label -> Either [Label] Label
travPt0 ls@(l0 : _) label
   | l0 /= label   = Left ls
   | otherwise     = Right label     ?

问题是,如果任何节点具有不匹配的标签,traverse将立即完全失败。 Traversable实际上并没有“有选择地”潜入数据结构的概念,它总是通过所有内容。实际上,我们只想在最顶层的节点上进行匹配,首先只需要匹配一个节点。

绕过立即深度遍历的一种方法是首先将树分割成子树树。好的,所以......我们需要提取最顶层的标签。我们需要在子树中拆分树。 Reminds you of anything

trav' :: (Traversable t, Comonad t) => [Label] -> t Label -> [Label]
trav' (l0 : ls) tree
  | top <- extract tree
             = if top /= l0 then l0 : ls
               else let subtrees = duplicate tree
                    in  ... ?

现在在这些子树中,我们基本上只对匹配的那个感兴趣。这可以从trav'的结果中确定:如果第二个元素再次传回,​​我们就会失败。与Either的正常命名法不同,这意味着我们希望继续,但不使用该分支!所以我们需要返回Either [Label] ()

               else case ls of
                     []     -> [l0]
                     l1:ls' -> let subtrees = duplicate tree
                               in  case traverse (trav' ls >>> \case
                                                     (l1':_)
                                                       | l1'==l1 -> Right ()
                                                     ls''        -> Left ls''
                                                 ) subtrees of
                                      Left ls'' -> ls''
                                      Right _   -> l0 : ls   -- no matches further down.

我还没有测试过这段代码

答案 1 :(得分:2)

我们将以下递归模型作为参考

import           Data.List   (minimumBy)
import           Data.Ord    (comparing)
import           Data.Tree

-- | Follows a path into a 'Tree' returning steps in the path which
-- are not contained in the 'Tree'
treeTail :: Eq a => [a] -> Tree a -> [a]
treeTail [] _ = []
treeTail (a:as) (Node a' trees)
  | a == a'   = minimumBy (comparing length) 
              $ (a:as) : map (treeTail as) trees
  | otherwise = as

这表明这里的机制不如我们遍历树累积(这是Traversable实例可能做的那样)但更多的是我们根据某些状态和搜索来遍历树为最深的道路。

如果我们愿意,我们可以通过Prism来描述这个“步骤”。

import Control.Lens

step :: Eq a => a -> Prism' (Tree a) (Forest a)
step a = 
  prism' (Node a)
         (\n -> if rootLabel n == a 
                  then Just (subForest n)
                  else Nothing)

这将允许我们将算法编写为

treeTail :: Eq a => [a] -> Tree a -> [a]
treeTail [] _ = []
treeTail pth@(a:as) t =
  maybe (a:as)
        (minimumBy (comparing length) . (pth:) . map (treeTail as))
        (t ^? step a)

但我不确定这是否更明确。