Haskell函数用于检查元素是否在Tree中,返回Depth

时间:2017-06-28 14:37:55

标签: haskell functional-programming

我正在为一个类做一个分配,在这个类中我必须实现一个函数来检查一个元素是否在树中。

当元素不在树中时,它应该返回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,然后使用了rl。 修正了两个错误。

有人写道我不需要使用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

3 个答案:

答案 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 ~ Treefind :: (a -> Bool) -> Tree a -> Maybe a。我给find的谓词是((== x) . snd),如果其输入元组的第二个元素等于Truex,则返回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左侧”rNode 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,您需要提供至少foldrdata 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