我正在为一个类做一个分配,在这个类中我必须实现一个函数来检查一个元素是否在树中。
当元素不在树中时,它应该返回Nothing
,而当它出现时,Just
(找到它的深度)应该返回。
一个例子:
sample1
##1
#3 2
###7 5 6 4
- contains 6 sample1 returns Just 2
- contains 1 sample1 returns Just 0
- contains 2 sample1 returns Just 1
- contains 8 sample1 returns Nothing
以下是我们的内容:
堆功能数据结构:
module Fdata.Heap where
-- A signature for min-heaps
data Heap e t = Heap {
empty :: t e,
insert :: e -> t e -> t e,
findMin :: t e -> Maybe e,
deleteMin :: t e -> Maybe (t e),
merge :: t e -> t e -> t e,
contains :: e -> t e -> Maybe Int
}
自我调整堆的实现:
import Fdata.Heap
import Fdata.Tree
-- An implementation of self-adjusting heaps
heap :: (Eq e, Ord e) => Heap e Tree
heap = Heap {
empty = Empty,
insert = \x t -> merge' (Node x Empty Empty) t,
findMin = \t -> case t of
Empty -> Nothing
(Node x _ _) -> Just x,
deleteMin = \t -> case t of
Empty -> Nothing
(Node _ l r) -> Just (merge' r l),
merge = \l r -> case (l, r) of
(Empty, t) -> t
(t, Empty) -> t
(t1@(Node x1 l1 r1), t2@(Node x2 l2 r2)) ->
if x1 <= x2
then Node x1 (merge' t2 r1) l1
else Node x2 (merge' t1 r2) l2,
contains = \x t -> case (x,t) of
(x,Empty)-> Nothing
(x,tx@(Node x1 l1 r1) ->
|x==x1 = Just 0
|x>x1 = (1+ (contains x l)
|x<x1 = (1+ (contains x r)
}
where
merge' = merge heap
树实现
module Fdata.Tree where
import Fdata.Heap
data Tree x
= Empty
| Node x (Tree x) (Tree x)
deriving (Eq, Show)
leaf x = Node x Empty Empty
-- Convert a list to a heap
list2heap :: Heap x t -> [x] -> t x
list2heap i = foldl f z
where
f = flip $ insert i
z = empty i
-- Convert a heap to a list
heap2list :: Heap x t -> t x -> [x]
heap2list i t
= case (findMin i t, deleteMin i t) of
(Nothing, Nothing) -> []
(Just x, Just t') -> x : heap2list i t'
我应该在实现中实现contains
函数来自我调整堆。
我不允许使用任何辅助函数,我应该使用maybe
函数。
我目前的实施:
contains = \x t -> case (x,t) of
(x,Empty) -> Nothing
(x,tx@(Node x1 l1 r1))
|x==x1 -> Just 0
|x>x1 -> (1+ (contains x l1)
|x<x1 -> (1+ (contains x r1)
这不起作用,因为我在输入|
上得到了解析错误。
我真的不知道如何解决这个问题,因为我确实使用了4个空格而不是标签,并根据这个:https://wiki.haskell.org/Case
语法正确......
我曾经设法解决这个问题,但是我收到了关于(1+ (contains x l)
的类型错误,所以这可能不正确。
任何暗示都会受到赞赏。
编辑: 感谢所有回答的人! 非常感谢每个人都花时间详细解释他们的答案。
首先: 正如你们中的一些人在评论中指出的那样,有一些较小的错误:
我错过了一个右括号,并且意外地命名了一个参数l1
和另一个r1
,然后使用了r
和l
。
修正了两个错误。
有人写道我不需要使用lambda函数。问题是当我使用类似的东西时:
contains _ Empty = Nothing
我收到错误:
解析输入错误&#39; _&#39;。
但是,lambda函数不会给我任何关于输入参数的错误。
目前唯一可以正常运行的功能是:
contains = \e t -> case (e,t) of
(_,Empty) -> Nothing
(e , Node x t1 t2) ->
if e == (head (heap2list heap (Node x t1 t2)))
then Just 0
else if (fmap (+1) (contains heap e t1))== Nothing
then (fmap (+1) (contains heap e t2))
else (fmap (+1) (contains heap e t1))
发现于: Counting/Getting "Level" of a hierarchical data
发现者:Krom
答案 0 :(得分:2)
构建contains :: Eq a => a -> Tree a -> Maybe Integer
的一种方法是首先标记树中每个元素的深度,使用类似this的内容,然后 fold 树找到你正在寻找的元素,用它拉出它的深度。没有太多代码就可以做到这一点!
在this answer停止的位置向右跳,这里是contains
。
contains :: Eq a => a -> Tree a -> Maybe Integer
contains x = fmap fst . find ((== x) . snd) . labelDepths
这就是整个功能!这是经典的函数式编程风格:我将代码结构化为可重用操作的管道,而不是手动设置一个定制的递归树遍历函数。在Haskell中,使用组合运算符(.)
构造管道,并从左到右读取。 labelDepths
的结果传递给find ((== x) . snd)
,其结果随后传递给fmap fst
。
labelDepths :: Tree a -> Tree (Integer, a)
,我在the answer I linked above中详细解释过,会在输入树的每个元素上附加Integer
深度。
find :: Foldable t => (a -> Bool) -> t a -> Maybe a
是一个标准函数,它提取满足谓词的容器的第一个元素(如树或列表)。在这种情况下,相关的Foldable
结构是Tree
,因此t ~ Tree
和find :: (a -> Bool) -> Tree a -> Maybe a
。我给find
的谓词是((== x) . snd)
,如果其输入元组的第二个元素等于True
:x
,则返回find ((== x) . snd) :: Tree (Integer, a) -> Maybe (Integer, a)
。 find
通过折叠输入结构工作 - 一次测试一个元素,直到找到与谓词匹配的元素。处理元素的顺序由容器的Foldable
实例定义,其实例更多。
fmap :: Functor f => (a -> b) -> f a -> f b
是另一个标准功能。它将映射函数统一应用于容器的每个元素,将其元素从类型a
转换为类型b
。这次有问题的容器是find
的返回值,它是Maybe
,所以fmap :: (a -> b) -> Maybe a -> Maybe b
。我提供的映射函数是fst
,它提取元组的第一个元素:fmap fst :: Maybe (Integer, a) -> Maybe Integer
。
所以把它们放在一起,你可以看到这是我对上述过程的英文描述的一个相当直接的实现。首先,我们用树的深度标记树中的每个元素,然后我们找到一个与我们正在寻找的项匹配的元素,然后我们提取元素之前被标记的深度。
我在上面提到Tree
是一个Foldable
容器。实际上,情况并非如此 - Foldable
没有Tree
的实例。获取Foldable
Tree
实例的最简单方法是启用DeriveFoldable
GHC扩展并说出神奇的字deriving Foldable
。
{-# LANGUAGE DeriveFoldable #-}
data Tree x = Empty | Node x (Tree x) (Tree x) deriving Foldable
这个自动实现的Foldable
实例将执行前序遍历,以自上而下的方式处理树。 (x
被视为“l
左侧”r
和Node x l r
中的Node
。您可以通过调整导出的遍历顺序来调整派生的遍历顺序。 Tree
构造函数。
那就是说,我猜这是一项任务,你不能修改Foldable
的定义或应用任何语言扩展。因此,您需要按照本文底部的模板手工编写自己的foldr
实例。这是instance Foldable Tree where
foldr f z Empty = z
foldr f z (Node x l r) = f x (foldr f (foldr f z r) l)
的实现,它执行前序遍历。
Node
foldr
案例很有意思。我们将树从右到左折叠(因为这是z
)并从下到上折叠。首先,我们折叠正确的子树,将Node
放在最右边的叶子上。然后我们使用右子树的聚合结果作为折叠左子树的种子。最后,我们使用折叠所有f x
个孩子的结果作为聚合器来应用Foldable
。
希望你没有发现这个答案太先进了! (很高兴回答你的任何问题。)虽然其他答案很好地展示了如何编写递归树遍历函数,但我真的想让你一瞥函数式编程的真正力量。当您在更高层次上思考时 - 将问题分解为组件部分,将操作构建为管道,并学习发现常见模式,如压缩,折叠和映射 - 您可以非常高效地使用非常少的代码解决问题。
二叉树的可折叠实例
要实例化foldMap
,您需要提供至少foldr
或data Tree a = Leaf
| Node (Tree a) a (Tree a)
instance Foldable Tree where
foldMap f Leaf = mempty
foldMap f (Node l x r) = foldMap f l `mappend` f x `mappend` foldMap f r
foldr f acc Leaf = acc
foldr f acc (Node l x r) = foldr f (f x (foldr f acc r)) l
的定义。
ghci> let myTree = Node (Node Leaf 'a' Leaf) 'b' (Node Leaf 'c' Leaf)
-- +--'b'--+
-- | |
-- +-'a'-+ +-'c'-+
-- | | | |
-- * * * *
ghci> toList myTree
"abc"
此实现执行树的in-order traversal。
DeriveFoldable
Foldable
扩展允许GHC根据类型的结构生成Node
个实例。我们可以通过调整data Inorder a = ILeaf
| INode (Inorder a) a (Inorder a) -- as before
deriving Foldable
data Preorder a = PrLeaf
| PrNode a (Preorder a) (Preorder a)
deriving Foldable
data Postorder a = PoLeaf
| PoNode (Postorder a) (Postorder a) a
deriving Foldable
-- injections from the earlier Tree type
inorder :: Tree a -> Inorder a
inorder Leaf = ILeaf
inorder (Node l x r) = INode (inorder l) x (inorder r)
preorder :: Tree a -> Preorder a
preorder Leaf = PrLeaf
preorder (Node l x r) = PrNode x (preorder l) (preorder r)
postorder :: Tree a -> Postorder a
postorder Leaf = PoLeaf
postorder (Node l x r) = PoNode (postorder l) (postorder r) x
ghci> toList (inorder myTree)
"abc"
ghci> toList (preorder myTree)
"bac"
ghci> toList (postorder myTree)
"acb"
构造函数的布局来改变机器编写的遍历的顺序。
{{1}}
答案 1 :(得分:0)
此功能不需要是lambda:
contains x t =
在案例中添加x
没有任何意义,因为您只需将其与x
匹配即可。您可以在函数头中使用模式匹配:
contains _ Empty = Nothing
Node
案例有三个子案例,其中搜索的值小于,大于或等于Node
中的值。如果您按照这种方式对它们进行排序,则可以从小于和大于测试中获得对称性,并且可以使用otherwise
处理相同的大小写。
重新发送时,您将获得一个Maybe Int
,您要添加一个Int
。您无法直接执行此操作,因为Maybe
位于maybe
内。通常情况下,你会解除添加,但我怀疑这是contains x (Node x' l r) | x < x' = maybe Nothing (Just . (+1)) $ contains x l
| x > x' = maybe Nothing (Just . (+1)) $ contains x r
| otherwise = Just 0
所需的呼叫应该去的地方(但看起来不自然):
maybe
(+1)
可能已被Maybe
fmap
(或<$>
)取代... = fmap (+1) $ contains ...
,而不是使用maybe
:
Nothing
使用Just
是不自然的,因为它必须明确传递dic = [{'0.0.0.0': {'Name': 'Zara', 'Age': 7, 'Class': 'First'}},{'1.1.1.1': {'Name': 'Aa', 'Age': 9, 'Class': 'Second'}}]
,并重新包装dic[0]['0.0.0.0']['Class'] += 'what'
。
答案 2 :(得分:0)
这不起作用,因为我在输入
上得到了解析错误|
你上一行错过了一个右括号。
我收到了关于
(1+ (contains x l))
的典型错误,所以这可能不正确。
这个想法完全正确,问题是contains x l
会返回Maybe Int
而不是Int
,因此您无法直接添加Just
。您只能在Just
时添加结果。有一个帮助函数正是如此,对Nothing
执行某些操作并保留Functor
s:fmap
(来自contains = \x t -> case (x,t) of
(x,Empty)-> Nothing
(x,tx@(Node x1 l1 r1))
|x==x1 -> Just 0
|x>x1 -> fmap (1+) (contains x l)
|x<x1 -> fmap (1+) (contains x r)
)。
contains x Empty = Nothing
contains x (Node v l r) = if x == v
then Just 0
else fmap (+1) $ contains x $ if x > v then l else r
是的,我把它写成
span