我按如下方式定义了一个树,并想检查给定元素是否是给定树的元素。这是我的代码,但它不能完全运行。它似乎只能起作用。
我是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)
答案 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
这是我们决定引入MyForest
和isForestElem
的第一个地方:我们可以在不考虑森林的情况下遵循树的结构。在这种情况下,我们发现某个节点包含一个林,因此我们立即知道isTreeElem (Node ...)
应该调用isForestElem
。为了实现这一点,我们不必考虑森林是什么或isForestElem
如何工作,因为这些决定都封装在isForestElem
函数中。
isForestElem
在某些时候,我们还需要实施isForestElem
。森林只是一个列表,因此我们通过模式匹配遵循[...]
的结构:
isForestElem [] y = ...
isForestElem (tree : forest) y = ...
对于[]
,没有地方可以搜索y
,因此我们返回False
:
isForestElem [] y = False
对于tree : forest
,y可以位于tree
或forest
中,因此我们调用相应的函数并将其结果与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
上运行的函数,可以遵循以下配方:始终实现每个函数的林和树版本。但在某些时候,您可能会注意到林版本都非常相似且有点无聊。他们觉得样板文件对你的程序没什么贡献,但无论如何都需要编写。如果是这种情况,您可能希望使用MyTree
和Prelude
模块中的列表操作,而不是编写自己的林操作。例如:
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(和函数式编程?)时,您可能希望专注于上面最火的版本的代码,使用单独的isTreeElem
和isTreeElem
函数。如果您仔细构建数据类型,那么代码结构应该遵循数据结构的简单原则可以帮助您实现目标。
PS。顺便说一下,AndrásKovács的答案中的isForestElem
技巧也遵循数据类型的结构,但更多地自动化。