дерево обхода с линзами и молниями

Я изучаю пакет Lens. Должен сказать, что это довольно сложная задача.

Может ли кто-нибудь показать мне, как пройти по дереву с застежкой-молнией из объектива? В частности, как я могу написать функцию, которая берет список корней и позволяет мне получить доступ к ветвям поддерева?

Предположим, у меня есть это дерево. Если мой ввод [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)?


person Kai    schedule 19.03.2013    source источник


Ответы (2)


edit: обычно я экспериментирую с ghci, чтобы понять новый код, поэтому для таких, как я, я создал Запись/страница School of Haskell, которая содержит приведенные ниже примеры, но их можно редактировать и запускать.


Думаю, что эти примеры ответят на ваши вопросы, но для целесообразности я собираюсь изменить другой узел. Мои знания о функциях молнии в линзе довольно поверхностны. На чтение и привыкание к типам в пакете lens требуется немного больше времени, чем во многих других пакеты, но потом не плохо. Я не использовал модуль молнии или модуль дерева в упаковке объектива до этого поста.

Деревья не очень хорошо работают с show, поэтому, если у меня будет время, я вернусь и добавлю несколько красивых распечаток, иначе, вероятно, ключом к работе в repl с этими примерами будет увидеть, что происходит.

Viewing

Если я хочу просмотреть значение первого узла, согласно пакет линз дерева, который называется корневым, то вы можете:

zipperTree & downward root & view focus

Изменение

Чтобы изменить это значение и воссоздать дерево (заархивируйте дерево):

zipperTree & downward root & focus .~ 10 & rezip

Если вы хотите двигаться вниз по ветвям, вам нужно использовать downward branches. Вот пример, который изменяет корень первой ветки и повторно архивирует дерево:

zipperTree & downward branches 
           & fromWithin traverse 
           & downward root 
           & focus .~ 5 
           & rezip

Здесь я перемещаюсь вниз к списку филиалов. Затем я использую fromWithin use use 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
person Davorak    schedule 19.03.2013
comment
Спасибо за это. Однако это не совсем относится к моей домашней работе (шучу). Я не пытаюсь изменить конкретный узел. Вместо этого я хочу обойти все дерево и записать путь к определенному узлу, который удовлетворяет некоторому условию. В вашем примере изменения вы знаете, что путь zipperTree & within (root.traverse.branches) >>= saveTape. Мне было интересно, как я могу получить путь, не зная его заранее (путем его прохождения). - person Kai; 19.03.2013
comment
Тогда был бы полезен конкретный пример с более подробной информацией. С помощью описанных выше примитивов и рекурсии можно посетить каждый узел в дереве, просмотреть каждое значение и применить к нему тест. После успешного выполнения теста вы просто вернете ленту или сохраните ее в монаде состояния или записи, если это лучше для вашего приложения. - person Davorak; 19.03.2013
comment
Это очень полезно! Как использовать Data.Tree.Lens для собственных типов деревьев? В частности, что, если это бинарное дерево вместо розового дерева? - person nont; 20.10.2013
comment
@nont root и branch — это просто линзы, определенные для розового дерева. Поэтому я бы использовал makeLenses, помощника TH, для создания линз для моей пользовательской структуры данных, будь то дерево или что-то еще. Затем используйте downward, focus, upward, fromWithin и rezip как обычно. - person Davorak; 21.10.2013

По моему опыту, обычно вам не нужна молния. В этом случае вы можете определить Traversal, который позволит вам получить доступ к подлесам по заданному пути корневых узлов.

-- 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
person glguy    schedule 09.05.2013