Haskell中的非树数据结构

时间:2014-01-18 07:53:22

标签: haskell

在Haskell中使树状数据结构相对容易。但是,如果我想要一个如下所示的结构怎么办?

    A (root)
   / \
  B   C
 / \ / \
D   E   F

因此,如果我通过B遍历结构以更新E,那么如果我遍历C,则返回的新更新结构也会更新E.

有人可以给我一些关于如何实现这一目标的提示吗?你可以假设没有循环。

2 个答案:

答案 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的所有值。意外碰撞不是问题,因为除了故意的碰撞之外不会发生碰撞。

这基本上是STRefIORef所做的,除了它们将实际值提升到monad的状态并隐藏身份。使用这些的唯一缺点是你需要使你的代码大部分是monadic,但无论你做什么,你都可能不会轻易摆脱它。 (修改值而不是替换它们本身就是有效的事情。)

您给出的结构没有详细说明,因此无法为您的用例定制示例,但这是一个使用the ST monadTree的简单示例:

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值的值,但它也根据需要在右侧改变了。


我应该注意到这些不是唯一的方法。对于同样的问题,可能还有许多其他解决方案,但它们都要求您明智地定义身份。只有完成一次,才能开始下一步。