Reputation: 177
I have defined a tree data type as follow:
data Tree a = T (Tree a, Tree a) | Leaf a deriving (Show, Eq, Ord)
and I want to filter this tree based on a certain condition. The function I tried to write is:
filterT :: (a -> Bool) -> Tree a -> Tree a
filterT c (T(Leaf x,T(t1,t2))) = if c x
then (T(Leaf x, T(filterT c t1, filterT c t2)))
else T(filterT c t1,filterT c t2)
this function is not working properly due to the fact that the function has nothing to return in the case where none of the two leaf values (at the end) satisfies the condition. I would be grateful if you could help.
Upvotes: 3
Views: 1703
Reputation: 77951
There is a possibility that no item in the entire tree satisfies the condition. Then you will have an empty tree. So you have to either expand the definition of tree to:
data Tree a = Empty | Leaf a | Node (Tree a) (Tree a)
that is, include the case for an Empty
tree or change the signature of filterT
to:
filterT :: (a -> Bool) -> Tree a -> Maybe (Tree a)
so that make it possible to return Nothing
.
Going with the 2nd approach (1), a possibility would be to derive a Foldable
instance and define the filter function in terms of right fold:
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving Show
instance Foldable Tree where
foldr f z (Leaf x) = f x z
foldr f z (Node l r) = foldr f (foldr f z r) l
filterT :: (a -> Bool) -> Tree a -> Maybe (Tree a)
filterT f = foldr go Nothing
where
go x z = if not (f x) then z else Just $ case z of
Nothing -> Leaf x
Just tr -> Node (Leaf x) tr
1. because it keeps the data type simpler and avoids redundant Node Empty Empty
values.
Upvotes: 3
Reputation: 531055
Part of the problem is that you simply don't have a way to represent an empty tree.
-- Get rid of unnecessary tuple
data Tree a = Empty
| Leaf a
| T (Tree a) (Tree a) deriving (Show, Eq, Ord)
-- A filtered empty tree is still empty
filterT p Empty = Empty
-- A leaf either stays the same or becomes empty
filterT p (Leaf x) | p x = Leaf x
| otherwise = Empty
-- Any other tree is just the result of filtering each child
filterT p (T left right) = T (filterT p left) (filterT p right)
This isn't a great fix; it still leaves you with multiple ways of representing essentially the same tree, since Empty
and T Empty Empty
aren't significantly different. You could write another function which "prunes" such trees.
prune :: Tree a -> Tree a
prune (T Empty Empty) = Empty
prune x = x
which can be incorporated into your filterT
function like so:
filterT _ Empty = Empty
filterT p (Leaf x) | p x = Leaf x
| otherwise = Empty
filterT p (T left right) = let prune (T Empty Empty) = Empty
prune x = x
in prune $ T (filterT p left) (filterT p right)
You might also extend prune
to contract single-leaf trees to a single leaf (should Tree (Leaf 3) Empty
and Leaf 3
be considered the same tree?), as in
filterT' _ Empty = Empty
filterT' p (Leaf x) | p x = Leaf x
| otherwise = Empty
filterT' p (T left right) = let prune (T Empty Empty) = Empty
prune (T (Leaf x) Empty)) = Leaf x
prune (T Empty (Leaf x)) = Leaf x
prune x = x
in prune $ T (filterT p left) (filterT p right)
Finally, whether or not you use prune
will depend on whether the filtered tree should preserve the structure of the original; maybe you want to distinguish between a leaf and a node that used to have two children, for example.
Upvotes: 3