给出一系列步骤:
>>> 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
。
答案 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)
但我不确定这是否更明确。