遍历和过滤haskell中的树

时间:2010-02-25 23:55:10

标签: haskell tree traversal generic-programming

我对Haskell很新(仍在努力完全理解monads)。我有一个问题,我有一个类似树的结构

type Tree = [DataA]

data DataA =  DataA1 [DataB] 
            | DataA2 String 
            | DataA3 String [DataA]
               deriving Show

data DataB =  DataB1 [DataA] 
            | DataB2 String 
            | DataB3 String [DataB]
               deriving Show

我想要做的是能够遍历这个并生成一个带过滤器的新树。例如,我可能想要将树中的所有DataB2更改为“foo”。

我看过树的例子,当它们在同一个数据部分时,构造函数是相似的。

在python世界中,我只是遍历列表,匹配我需要的任何内容,并替换值。

在Haskell中我猜我需要能够复制我的树,但是你如何处理隐藏在构造函数和不同数据类型中的列表呢?

4 个答案:

答案 0 :(得分:10)

您可以使用泛型编程。

一个这样的通用编程库叫做Scrap Your Boilerplate。在模块的最顶层,通过编写:

启用Scrap Your Boilerplate
{-# LANGUAGE DeriveDataTypeable #-}

导入模块Data.Generics。除Show之外,还为您的数据类型派生TypeableData个实例。

现在您可以像这样编写您要求的功能:

toFoo :: Data a => a -> a
toFoo = everywhere (mkT step)
  where
    step (DataA2 _)  = DataA2 "foo"
    step x           = x

这就是你需要做的就是让它发挥作用。例如,当您致电toFoo [DataA1 [], DataA2 "hi", DataA3 "yo" []]时,答案为[DataA1 [],DataA2 "foo",DataA3 "yo" []]

希望这有帮助!

答案 1 :(得分:2)

我不知道你问题的一般答案。数据类型非常人为,我可能会选择实现折叠而不是过滤器。但是,这里有一些过滤函数可以更新所有四个位置的字符串。我已经把代码放在了编译器中,所以它有了类型,但我还没有运行它。

type SFilter = String -> String

-- to filter a tree, say how A2, A3, B2, and B3 should be changed

type Filter tree = SFilter -> SFilter -> SFilter -> SFilter -> (tree -> tree)

afilter :: Filter DataA
bfilter :: Filter DataB
tfilter :: Filter Tree

tfilter a2 a3 b2 b3 = map (afilter a2 a3 b2 b3)
afilter a2 a3 b2 b3 = fil
  where fil (DataA1 bs)   = DataA1 $ map (bfilter a2 a3 b2 b3) bs
        fil (DataA2 s)    = DataA2 (a2 s)
        fil (DataA3 s as) = DataA3 (a3 s) (map fil as)

bfilter a2 a3 b2 b3 = fil
  where fil (DataB1 as)   = DataB1 $ map (afilter a2 a3 b2 b3) as
        fil (DataB2 s)    = DataB2 (b2 s)
        fil (DataB3 s bs) = DataB3 (b3 s) (map fil bs)

答案 2 :(得分:2)

您希望遍历整个数据结构并在此处更改某些项目。 这通常由将数据结构作为参数的函数来完成 返回结构的新的更改版本。

对于输入的每个情况,此函数定义返回的新值的方式 看起来像。

修改Tree的基本功能(只是DataA值的列表) 可能应该只返回一个新的修改值列表。如果我们推迟那些 将值修改为modifyA函数,主要修改 功能看起来像这样:

-- # function to change a |Tree|
mutate :: Tree -> Tree
mutate as = map mutateA as
     -- # (The |map| function applies the |mutateA| function to every
     -- #  element of |as|, creating a list of all the return values)

现在需要定义mutateA函数来更改所有可能的DataA值,并且 最好它附带一个处理mutateB值的DataB函数。

这些函数查看值的不同可能情况并返回 适当的新值:

-- # function to change |DataA| items
mutateA :: DataA -> DataA
     -- # A |DataA1| is a |DataA1| with modified values
mutateA (DataA1 bs)   = DataA1 (map mutateB bs)
     -- # A |DataA3| is a |DataA3| with modified values
mutateA (DataA3 s as) = DataA3 s (map mutateA as)
     -- # In the remaining case(s) the value stays the same
mutateA d             = d

-- # function to change |DataB| items
mutateB :: DataB -> DataB
mutateB (DataB1 as) = DataB1 (map mutateA as)
mutateB (DataB3 s bs) = DataB3 s (map mutateB bs)
     -- # Here comes a real change
mutateB (DataB2 _)  = DataB2 "foo"

这种方式为树中的每个元素计算一个新元素,其中所有DataB2 树中任何地方的值都被“foo”替换。

它相对冗长,因为你有五个不同的案例包含一个列表 需要通过的值,但这不是Haskell特有的。在 一种命令式语言,你通常会有五个for循环代替五个 致电map

也许您可以简化数据结构以减少这种“开销”。这个 当然取决于您的实际用例,但也许,例如,您不需要 Data2个案: DataA2 "abc"DataA3 "abc" []之间有区别吗?

答案 3 :(得分:0)

您可能需要查看multirec库以处理相互递归的数据类型。我没有使用它,但从你所描述的内容来看,它听起来就像是你正在使用的那种问题。它使用泛型编程,就像这里提出的其他答案一样,但可能会节省你自己实现所有内容的时间。