Reputation: 33
I am currently doing an assigment for a class in which I have to implement a function which checks if an element is in a tree.
It is supposed to return Nothing
when the element is not in the tree and Just
(depth at which it was found) when it is.
An example:
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
Here is what we are given:
Heap functional data structure:
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
}
An implementation of self-adjusting heaps:
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
The tree implementation
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'
I am supposed to implement the contains
function in the implementation for self-adjusting heaps.
I am not allowed to use any helper functions and I am supposed to use the maybe
function.
My current implementation:
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)
This does not work, since I get a parse error on input |
.
I really dont know how to fix this since I did use 4 spaces instead of tabs and according to this: https://wiki.haskell.org/Case
the syntax is correct...
I once managed to fix this, but I got a type error about (1+ (contains x l)
, so this probably is not correct.
Any hint would be appreciated.
EDIT: Thanks to everyone who answered! Really appreciate that everyone took the time to explain their answers in great detail.
First of all: there were some smaller mistakes, as pointed out by some of you in the comments:
I missed one closing parenthesis and accidentially named one argument l1
and another r1
and afterwards used r
and l
.
Fixed both mistakes.
Someone wrote that I do not need to use a lambda function. The problem is when I use something like:
contains _ Empty = Nothing
I get the error:
parse Error on input '_'.
However, lambda functions do not give me any errors about the input arguments.
Currently the only function that works without any errors is:
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))
Found at: Counting/Getting "Level" of a hierarchical data
Found by:Krom
Upvotes: 2
Views: 2014
Reputation: 44654
One way of structuring contains :: Eq a => a -> Tree a -> Maybe Integer
is to first label each element in your tree with its depth, using something like this, then fold the tree to find the element you're looking for, pulling its depth out with it. You can do this without very much code!
Jumping right in where this answer left off, here's contains
.
contains :: Eq a => a -> Tree a -> Maybe Integer
contains x = fmap fst . find ((== x) . snd) . labelDepths
That's the whole function! This is classic functional programming style: rather than hand-crank a bespoke recursive tree traversal function I've structured the code as a pipeline of reusable operations. In Haskell pipelines are constructed using the composition operator (.)
and are read from left to right. The result of labelDepths
is passed to find ((== x) . snd)
, whose result is then passed to fmap fst
.
labelDepths :: Tree a -> Tree (Integer, a)
, which I've explained in detail in the answer I linked above, attaches an Integer
depth to each element of the input tree.
find :: Foldable t => (a -> Bool) -> t a -> Maybe a
is a standard function which extracts the first element of a container (like a tree, or a list) that satisfies a predicate. In this instance, the Foldable
structure in question is a Tree
, so t ~ Tree
and find :: (a -> Bool) -> Tree a -> Maybe a
. The predicate I've given to find
is ((== x) . snd)
, which returns True
if the second element of its input tuple equals x
: find ((== x) . snd) :: Tree (Integer, a) -> Maybe (Integer, a)
. find
works by folding the input structure - testing its elements one at a time until it finds one that matches the predicate. The order in which elements are processed is defined by the container's Foldable
instance, of which more below.
fmap :: Functor f => (a -> b) -> f a -> f b
is another standard function. It applies a mapping function uniformly to each element of a container, transforming its elements from type a
to type b
. This time the container in question is the return value of find
, which is a Maybe
, so fmap :: (a -> b) -> Maybe a -> Maybe b
. The mapping function I've supplied is fst
, which extracts the first element of a tuple: fmap fst :: Maybe (Integer, a) -> Maybe Integer
.
So putting it all together, you can see that this is a fairly direct implementation of my English description of the process above. First we label every element in the tree with its depth, then we find an element which matches the item we're looking for, then we extract the depth with which the element was previously labelled.
I mentioned above that Tree
is a Foldable
container. In fact, this isn't the case quite yet - there's no instance of Foldable
for Tree
. The easiest way to get a Foldable
instance for Tree
is to turn on the DeriveFoldable
GHC extension and utter the magic words deriving Foldable
.
{-# LANGUAGE DeriveFoldable #-}
data Tree x = Empty | Node x (Tree x) (Tree x) deriving Foldable
This automatically-implemented instance of Foldable
will perform a preorder traversal, processing the tree in a top-down fashion. (x
is considered to be "to the left of" l
and r
in the expression Node x l r
.) You can adjust the derived traversal order by adjusting the layout of the Node
constructor.
That said, I'm guessing that this is an assignment and you're not allowed to modify the definition of Tree
or apply any language extensions. So you'll need to hand-write your own instance of Foldable
, following the template at the bottom of this post. Here's an implementation of foldr
which performs a preorder traversal.
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)
The Node
case is the interesting one. We fold the tree from right to left (since this is a foldr
) and from bottom to top. First we fold the right subtree, placing z
at the rightmost leaf. Then we use the aggregated result of the right subtree as the seed for folding the left subtree. Finally we use the result of folding all of the Node
's children as the aggregator to apply to f x
.
Hopefully you didn't find this answer too advanced! (Happy to answer any questions you have.) While the other answers do a good job of showcasing how to write recursive tree traversal functions, I really wanted to give you a glimpse of the real power of functional programming. When you think at a higher level - breaking down a problem into its component parts, structuring operations as pipelines, and learning to spot common patterns like zipping, folding and mapping - you can be very productive and solve problems with very little code.
An instance of Foldable for a binary tree
To instantiate Foldable
you need to provide a definition for at least foldMap
or 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
This implementation performs an in-order traversal of the tree.
ghci> let myTree = Node (Node Leaf 'a' Leaf) 'b' (Node Leaf 'c' Leaf)
-- +--'b'--+
-- | |
-- +-'a'-+ +-'c'-+
-- | | | |
-- * * * *
ghci> toList myTree
"abc"
The DeriveFoldable
extension allows GHC to generate Foldable
instances based on the structure of the type. We can vary the order of the machine-written traversal by adjusting the layout of the Node
constructor.
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"
Upvotes: 2
Reputation: 665485
This does not work, since I get a parse error on input
|
Your previous line misses a closing parenthesis.
I got a Typ error about
(1+ (contains x l))
, so this probably is not correct.
The idea is totally correct, the issue is that contains x l
returns a Maybe Int
instead of an Int
so you cannot directly add to that. You can only add to the result when it's a Just
. There's a helper function that does exactly that, do something to Just
s and keep Nothing
s: fmap
(from Functor
).
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)
Btw, I'd write this as
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
Upvotes: 0
Reputation: 12749
This function doesn't need to be a lambda:
contains x t =
Adding x
to the case serves no purpose, since you only match it back to x
. You can instead use pattern matching in the function head:
contains _ Empty = Nothing
The Node
case has three sub-cases, where the value being searched for is less-than, greater-than, or equal to the value in the Node
. If you order them that way, you get a symmetry from the less-than and greater-than tests, and can handle the equal case with an otherwise
.
When recusring, you are going to get a Maybe Int
, to which you want to add one. You can't do that directly because the Int
is inside the Maybe
. Normally, you would lift the addition, but I suspect that this is where the required call to maybe
should go (however unnatural it may seem):
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
Instead of using maybe
, the (+1)
could have been lifted into the Maybe
with fmap
(or <$>
):
... = fmap (+1) $ contains ...
Using maybe
is unnatural because it has to explicitly pass the Nothing
, and also re-wrap the Just
.
Upvotes: 0