检查树中是否存在元素

时间:2014-03-02 01:52:58

标签: haskell

我按如下方式定义了一个树,并想检查给定元素是否是给定树的元素。这是我的代码,但它不能完全运行。它似乎只能起作用。

我是Haskell的新手,无法弄清楚如何解决这个问题。

data MyTree a = Leaf a | Node [MyTree a] deriving (Eq)

isElem :: (Eq a) => MyTree a -> MyTree a -> Bool
isElem (Node []) _ = error
isElem (Node (h:t)) (Leaf a) 
    | (Leaf a) `elem` (h:t) = True
    | otherwise = False

这是我的第二次尝试。

isElem :: (Eq a) => MyTree a -> MyTree a -> Bool
isElem (Node []) a = False
isElem (Node (h:t)) a
    | (a == h) = True
    | otherwise = (isElem (Node t) a)

3 个答案:

答案 0 :(得分:5)

我假设你对自己实现这个功能感兴趣(并且bheklir解决了这个问题),但你也可以自动导出它:

{-# LANGUAGE DeriveFoldable #-}

import qualified Data.Foldable as F

data MyTree a = Leaf a | Node [MyTree a] deriving (Eq, F.Foldable)

isElem = F.elem 

答案 1 :(得分:4)

如果我们有二叉树

data BinTree a
    = Empty
    | Branch a (BinTree a) (BinTree a)
    deriving (Eq, Show)

然后我们可以进行搜索

isElem :: (Eq a) => a -> BinTree a -> Bool
isElem a Empty = False
isElem a (Branch c left right)
    | a == c    = True
    | otherwise = isElem a left || isElem a right

请注意,在第二名后卫中,我递归地在左侧和右侧叶子上调用isElem。如果其中一个返回True,那么整体返回值将为True,因为逻辑或(||)。这种递归是算法的关键。

我想让你考虑如何将递归应用于不仅仅是2个分支,而是N个分支,然后使用或组合它们。另外,我建议您将函数定义为

isElem :: (Eq a) => a -> MyTree a -> Bool

避免使用error。此功能永远不会出错。要么元素在树中,要么不在,它没有第三个状态。只需返回False

答案 2 :(得分:3)

在这个答案中,我解释了如何通过从数据类型isElem的结构开始实现MyTree

遵循结构

您的isElem函数应在整个MyTree中查找元素,因此isElem的结构可能与MyTree的结构类似。

data MyTree a = Leaf a | Node [MyTree a]

为了使MyTree的结构更清晰,让我们将[MyTree a]称为森林,因为森林包含多棵树。

data MyTree a = Leaf a | Node (MyForest a)
type MyForest a = [MyTree a]

现在我们有两种类型,这有助于我们理解我们需要两个函数:

isTreeElem :: MyTree a -> a -> Bool
isForestElem :: MyForest a -> a -> Bool

我将第二个参数从MyTree a更改为a,正如bheklilr的回答中提出的那样。

实施isTreeElem

让我们从isTreeElem开始吧。我们通过模式匹配遵循MyTree的结构:

isTreeElem (Leaf x) y = ...
isTreeElem (Node forest) y = ...

Leaf的等式中,树中只有一个y所在的位置。因此,如果True,则返回x == y,如果False则返回x /= y。换句话说:

isTreeElem (Leaf x) y = x == y

在等式Node中,我们需要在整个森林中继续搜索y。所以我们只需拨打isForestElem

isTreeElem (Node forest) y = isForestElem forest y

这是我们决定引入MyForestisForestElem的第一个地方:我们可以在不考虑森林的情况下遵循树的结构。在这种情况下,我们发现某个节点包含一个林,因此我们立即知道isTreeElem (Node ...)应该调用isForestElem。为了实现这一点,我们不必考虑森林是什么或isForestElem如何工作,因为这些决定都封装在isForestElem函数中。

实施isForestElem

在某些时候,我们还需要实施isForestElem。森林只是一个列表,因此我们通过模式匹配遵循[...]的结构:

isForestElem [] y = ...
isForestElem (tree : forest) y = ...

对于[],没有地方可以搜索y,因此我们返回False

isForestElem [] y = False

对于tree : forest,y可以位于treeforest中,因此我们调用相应的函数并将其结果与or运算符{{ 1}}:

||

这是我们决定引入isForestElem (tree : forest) y = isTreeElem tree y || isForestElem forest y MyForest的第二个地方:我们可以在不考虑树木的情况下遵循森林结构。在这种情况下,我们看到一个森林包含一棵树,因此我们立即知道isForestElem应该调用isForestElem (... : ...)。为了实现这一点,我们不必考虑树是什么或isTreeElem如何工作,因为这些决定都封装在isTreeElem函数中。

请注意,我们可以先实施isTreeElem,然后isForestElem秒,而不会改变任何推理。

代码

这是我们到目前为止所拥有的:

isTreeElem

使用列表功能

如果要编写更多可在data MyTree a = Leaf a | Node (MyForest a) type MyForest a = [MyTree a] isTreeElem :: MyTree a -> a -> Bool isTreeElem (Leaf x) y = x == y isTreeElem (Node forest) y = isForestElem forest y isForestElem :: MyForest a -> a -> Bool isForestElem [] y = False isForestElem (tree : forest) y = isTreeElem tree y || isForestElem forest y 上运行的函数,可以遵循以下配方:始终实现每个函数的林和树版本。但在某些时候,您可能会注意到林版本都非常相似且有点无聊。他们觉得样板文件对你的程序没什么贡献,但无论如何都需要编写。如果是这种情况,您可能希望使用MyTreePrelude模块中的列表操作,而不是编写自己的林操作。例如:

Data.List

或者,如果我们更改isForestElem forest x = any (\tree -> isTreeElem tree x) forest isTreeElem的参数顺序,我们甚至可以写:

isForestElem

现在isTreeElem :: a -> Tree a -> Bool isTreeElem = ... isForestElem :: a -> Forest a -> Bool isForestElem x = any (isTreeElem x) 非常简单,我们可能希望将其内联到isForestElem

isTreeElem

在某些时候,您应该开始编写更像isTreeElem :: a -> MyTree a -> Bool isTreeElem y (Leaf x) = x == y isTreeElem y (Node forest) = any (isTreeElem y) forest 的上一版本的代码。但作为一个中间步骤,当您学习Haskell(和函数式编程?)时,您可能希望专注于上面最火的版本的代码,使用单独的isTreeElemisTreeElem函数。如果您仔细构建数据类型,那么代码结构应该遵循数据结构的简单原则可以帮助您实现目标。

PS。顺便说一下,AndrásKovács的答案中的isForestElem技巧也遵循数据类型的结构,但更多地自动化。