在Haskell中使树状数据结构相对容易。但是,如果我想要一个如下所示的结构怎么办?
A (root)
/ \
B C
/ \ / \
D E F
因此,如果我通过B遍历结构以更新E,那么如果我遍历C,则返回的新更新结构也会更新E.
有人可以给我一些关于如何实现这一目标的提示吗?你可以假设没有循环。
答案 0 :(得分:3)
我会将数据结构扁平化为数组,而是对此进行操作:
import Data.Array
type Tree = Array Int -- Bounds should start at (1) and go to sum [1..n]
data TreeTraverse = TLeft TreeTraverse | TRight TreeTraverse | TStop
给定一些遍历方向(左,右,停),很容易看出,如果我们向左移动,我们只需将当前级别添加到我们的位置,如果我们向右移动,我们还会添加当前位置加一:
getPosition :: TreeTraverse -> Int
getPosition = getPosition' 1 1
where
getPosition' level pos (TLeft ts) = getPosition' (level+1) (pos+level) ts
getPosition' level pos (TRight ts) = getPosition' (level+1) (pos+level + 1) ts
getPosition' _ pos (TStop) = pos
在您的情况下,您想要遍历ABE或ACE:
traverseABE = TLeft $ TRight TStop
traverseACE = TRight $ TLeft TStop
由于我们现在已经知道如何获取元素的位置,并且Data.Array
提供了一些设置/获取特定元素的函数,我们可以使用以下函数来获取/设置树值:
getElem :: TreeTraverse -> Tree a -> a
getElem tt t = t ! getPosition tt
setElem :: TreeTraverse -> Tree a -> a -> Tree a
setElem tt t x = t // [(getPosition tt, x)]
要完成代码,请使用您的示例:
example = "ABCDEF"
exampleTree :: Tree Char
exampleTree = listArray (1, length example) example
并将所有内容都放到action:
main :: IO ()
main = do
putStrLn $ "Traversing from A -> B -> E: " ++ [getElem traverseABE exampleTree]
putStrLn $ "Traversing from A -> C -> E: " ++ [getElem traverseACE exampleTree]
putStrLn $ "exampleTree: " ++ show exampleTree ++ "\n"
putStrLn $ "Setting element from A -> B -> E to 'X', "
let newTree = setElem traverseABE exampleTree 'X'
putStrLn $ "but show via A -> C -> E: " ++ [getElem traverseACE newTree]
putStrLn $ "newTree: " ++ show newTree ++ "\n"
请注意,这很可能不是最好的方法,但这是我想到的第一件事。
答案 1 :(得分:3)
一旦你建立了身份,就可以完成。
但首先你必须建立身份。
在许多语言中,价值观可以彼此不同,但相等。在Python中,例如:
>>> a = [1]
>>> b = [1]
>>> a == b
True
>>> a is b
False
您希望在树的一个分支中更新E,并且还更新该元素 E的所有其他元素。但Haskell是引用透明的:它没有相同的概念宾语;只有相等,甚至不适用于每个对象。
你可以做到这一点的一种方法是平等。说这是你的树:
__A__
/ \
B C
/ \ / \
1 2 2 3
然后我们可以浏览树并将所有2更新为,例如,4。但在某些情况下,这并不是你想要的。
在Haskell中,如果你想在多个地方更新一件事,你必须明确是而不是是同一件事。另一种处理方法是使用唯一的整数标记每个不同的值,并使用该整数来确定标识:
____________A___________
/ \
B C
/ \ / \
(id=1)"foo" (id=2)"bar" (id=2)"bar" (id=3)"baz"
然后我们可以更新标识为2的所有值。意外碰撞不是问题,因为除了故意的碰撞之外不会发生碰撞。
这基本上是STRef
和IORef
所做的,除了它们将实际值提升到monad的状态并隐藏身份。使用这些的唯一缺点是你需要使你的代码大部分是monadic,但无论你做什么,你都可能不会轻易摆脱它。 (修改值而不是替换它们本身就是有效的事情。)
您给出的结构没有详细说明,因此无法为您的用例定制示例,但这是一个使用the ST
monad和Tree
的简单示例:
import Control.Monad
import Control.Monad.ST
import Data.Tree
import Data.Traversable (traverse)
import Data.STRef
createInitialTree :: ST s (Tree (STRef s String))
createInitialTree = do
[a, b, c, d, e, f] <- mapM newSTRef ["A", "B", "C", "D", "E", "F"]
return $ Node a [ Node b [Node d [], Node e []]
, Node c [Node e [], Node f []]
]
dereferenceTree :: Tree (STRef s a) -> ST s (Tree a)
dereferenceTree = traverse readSTRef
test :: ST s (Tree String, Tree String)
test = do
tree <- createInitialTree
before <- dereferenceTree tree
let leftE = subForest (subForest tree !! 0) !! 1
writeSTRef (rootLabel leftE) "new" -- look ma, single update!
after <- dereferenceTree tree
return (before, after)
main = do
let (before, after) = runST test
putStrLn $ drawTree before
putStrLn $ drawTree after
观察虽然我们只是明确地修改了左E值的值,但它也根据需要在右侧改变了。
我应该注意到这些不是唯一的方法。对于同样的问题,可能还有许多其他解决方案,但它们都要求您明智地定义身份。只有完成一次,才能开始下一步。