我正在学习镜头包。我必须说这是一项相当具有挑战性的任务。
有人可以告诉我如何使用Zipper从镜头遍历一棵树吗?特别是,如何编写一个带有根列表并允许我访问子树分支的函数?
假设我有这棵树。如果我的输入是[1, 3]
,则该函数应允许我访问节点10和11.
import Control.Lens
import Data.Tree
import Data.Tree.Lens
testTree = Node 1 [ Node 2 [ Node 4 [ Node 6 [], Node 8 [] ],
Node 5 [ Node 7 [], Node 9 [] ] ],
Node 3 [ Node 10 [],
Node 11 [] ]
]
zipperTree = zipper testTree
此外,我如何使用saveTape
和restoreTape
来保存遍历路径(到StateT或IORef)?
答案 0 :(得分:16)
编辑:我通常会尝试使用ghci来理解新代码,因此对于像我这样的人,我创建了一个School of Haskell post/page,它带有以下示例,但它们是可编辑且可运行的。
认为这些示例将回答您的问题,但为了方便起见,我将修改一个不同的节点。我对拉链功能的了解 lens相当浅薄。阅读并习惯于中的类型需要更长的时间 lens包与许多其他包相比,但之后也不错。在本文之前,我没有在镜头包装中使用过拉链模块或树模块。
对于show
,树木的效果不是很好,所以如果我有时间,我会回来添加一些漂亮的打印输出,否则使用这些示例在repl中工作可能是关键,看看发生了什么
如果我想查看第一个节点的值,根据tree lens package这被称为根,那么你可以:
zipperTree & downward root & view focus
要修改该值并重新创建树(重新压缩树):
zipperTree & downward root & focus .~ 10 & rezip
如果您想向下移动分支,则需要使用downward branches
。下面是一个修改第一个分支的根并重新编码树的示例:
zipperTree & downward branches
& fromWithin traverse
& downward root
& focus .~ 5
& rezip
在这里,我向下移动到分支列表。然后我使用fromWithin
使用traverse
来遍历列表,如果这是一个元组,我可以使用both
代替。
saveTape
和restoreTape
允许您将您的位置保存在拉链中,以便后者可以恢复。
保存职位:
tape = zipperTree & downward branches
& fromWithin traverse
& downward root
& saveTape
然后,我可以通过树重新创建遍历:
t <- (restoreTape tape testTree)
然后你可以使用t作为新的拉链并正常修改它:
t & focus .~ 15 & rezip
磁带重放您所采取的步骤,因此可以在其他树上工作,因此以下内容适用于上面定义的磁带:
testTree2 = Node 1 [ Node 2 [] ]
t2 <- (restoreTape tape testTree2)
t2 & focus .~ 25 & rezip
如果要修改多个根,只需按住重新拉链拉链即可。以下内容修改了testTree2的两个根:
zipper testTree2 & downward root
& focus .~ 11
& upward
& downward branches
& fromWithin traverse
& downward root
& focus .~ 111
& rezip
答案 1 :(得分:4)
根据我的经验,您通常不需要 Zipper 。在这种情况下,您可以定义遍历,这将允许您在给定根节点路径的情况下访问子林。
-- Make things a little easier to read
leaf :: a -> Tree a
leaf = return
testForest :: Forest Int
testForest = [ Node 1 [ Node 2 [ Node 4 [ leaf 6, leaf 8]
, Node 5 [ leaf 7, leaf 9]]
, Node 3 [ leaf 10, leaf 11]]]
-- | Handy version of foldr with arguments flipped
foldEndo :: (a -> b -> b) -> [a] -> b -> b
foldEndo f xs z = foldr f z xs
-- | Traverse the subforests under a given path specified by the values of
-- the parent nodes.
path :: Eq a => [a] -> Traversal' (Forest a) (Forest a)
path = foldEndo $ \k -> -- for each key in the list
traverse -- traverse each tree in the forest
. filtered (hasRoot k) -- traverse a tree when the root matches
. branches -- traverse the subforest of the tree
where
hasRoot :: Eq a => a -> Tree a -> Bool
hasRoot k t = k == view root t
-- | Helper function for looking at 'Forest's.
look :: Show a => Forest a -> IO ()
look = putStr . drawForest . (fmap . fmap) show
-- Just show 'path' in action
demo1 = look $ testForest & path [1,3] .~ []
demo2 = look $ testForest & path [1,3] . traverse . root *~ 2
demo3 = look $ testForest ^. path [1,3]
demo4 = [] == testForest ^. path [9,3]
demo5 = traverseOf_ (path [1,3]) print testForest