如何在Haskell Data.Tree中查找节点的路径

时间:2014-01-28 08:11:23

标签: haskell tree

给定Haskell中的一个树(由Data.Tree表示),我怎样才能找到节点的路径?

e.g。

import Data.Tree

tree = Node 1 [Node 2 [Node 3 []], Node 4 []]

形成一棵看起来像的树:

1
|
+- 2
|  |
|  `- 3
|
`- 4

我怎样才能创建一个函数pathToNode

pathToNode 0 tree => []
pathToNode 1 tree => [1]
pathToNode 2 tree => [1, 2]
pathToNode 3 tree => [1, 2, 3]
pathToNode 4 tree => [1, 4]

在我的特定情况下,任何给定的值只会在树中出现一次,因此可以接受将 a 路径返回到值的解决方案。

到目前为止,我最好的答案是:

pathToNode :: (Eq a) => a -> Tree a -> [a]
pathToNode x (Node y ys) | x == y    = [x]
                         | otherwise = case concatMap (pathToNode x) ys of
                                         [] -> []
                                         path -> y:path

有没有更简洁的写作方式?是否可以利用Data.FoldableData.Traversable来避免编写自己的遍历逻辑?

2 个答案:

答案 0 :(得分:5)

这里不能使用默认的TraversableFoldable实例,因为它们没有提供足够的上下文信息来维护路径(例如,当遍历State monad时) 。它们都以某种顺序访问树的每个元素,因此您无法知道某些先前访问过的值是属于当前节点的父节点还是兄弟节点。

我认为以下功能足够简洁:

pathsToNode :: Eq a => a -> Tree a -> [[a]]
pathsToNode x (Node y ns) = [[x] | x == y] ++ map (y:) (pathsToNode x =<< ns)

它列出了x的所有副本的路径,但如果这是您想要的,您可以随便懒散地找到第一个找到的路径。

答案 1 :(得分:5)

存在称为catamorphism的折叠概念的概括。与折叠允许您“消耗”列表而没有显式递归的方式相同,使用catamorphism可以“消耗”一个树或其他数据类型,而无需显式递归,并以自下而上的方式从叶子开始。与常规折叠不同,它将了解树的结构。

可以在包cata的模块Data.Functor.Foldable(不是Data.Foldable!)中找到recursion-schemes函数。不幸的是,它不能与Data.Tree一起使用,你必须以间接的两步方式定义一个等效的数据类型:

{-# LANGUAGE DeriveFunctor #-}

import Data.Functor.Foldable

data Node a b = Node a [b] deriving (Functor,Eq)

type Tree a = Fix (Node a)

tree :: Tree Int
tree = Fix (Node 1 [ Fix ( Node 2 [ Fix (Node 3 []) ]), 
                     Fix ( Node 4 [] ) ])

使用cata,我们可以构建树中所有值的所有路径的列表。注意缺少显式递归:

paths :: Tree a -> [(a,[a])]
paths = cata algebra  
    where
    algebra :: Node a [(a,[a])] -> [(a,[a])]
    algebra (Node a as) = (a,[a]) : map (\(i,is)->(i,a:is)) (concat as) 

从该函数中,我们可以定义pathToNode:

pathToNode :: (Eq a) => a -> Tree a -> [a]
pathToNode a = snd . head . filter ((==a).fst) . paths 

这种解决方案并不是我所害怕的,但是catamorphims是一个很有用的工具。