问题
I need to make function returns all possible branches from a tree with this form:
data Tree a = EmptyT | NodeT a ( Tree a ) ( Tree a ) deriving (Show)
everyBranch :: Tree a -> [[a]]
I'm not sure how to approach this... xD I'm still a newbie in Haskell.
Let's say that I have:
1
/ \
2 3
/\ / \
4 5 7 8
I want to get: [[1,2,4], [1,2,5], [1,3,8], [1,3,7]]
回答1:
We'll use a recursive approach. Let's start with a rough skeleton:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v (Tree l) (Tree r)) = _somethingElse
Now we'll fill in the holes. (This syntax is known as 'typed holes': if you run the above program through GHC, it will give you an error message with the type of the value which should be in the hole.) Now, I'm not sure about the first case: depending on your need, it could be []
(no branches) or [[]]
(one branch with no elements), so we'll come back to this later. For the second case, we need a way to construct a list of branches given the v
alue and the l
eft and r
ight subtrees. How do we do that? We'll recursively find every branch in the l
eft tree, and every branch in the r
ight tree, and then we'll prepend v
to both:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v l r) = map (v:) $ everyBranch l ++ everyBranch r
Now, let's go back to EmptyT
. Consider a very simple tree: NodeT 1 EmptyT EmptyT
. In this case, everyBranch
should return [[1]]
. Let's invoke everyBranch
'by hand' on this tree:
(I use └→
to mean 'evaluate sub-expression recursively', and =>
meaning 'expression evaluates to')
everyBranch (NodeT 1 EmptyT EmptyT)
=> map (1:) $ everyBranch EmptyT ++ everyBranch EmptyT
└→ everyBranch EmptyT
=> _something
=> map (1:) $ _something ++ _something
So here, we want map (1:) $ _something ++ _something
to be equal to [[1]]
. What is _something
? Well, it turns out that if _something
is []
, then map (1:) $ [] ++ []
is []
, which isn't what we want. On the other hand, if _something
is [[]]
, then map (1:) $ [[]] ++ [[]]
is [[1], [1]]
- which isn't what we want either. It looks like we need a slightly different approach. What we'll do is, we'll add another case specifically for these sort of trees:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v EmptyT EmptyT) = [[v]]
everyBranch (NodeT v l r) = map (v:) $ everyBranch l ++ everyBranch r
Now, if we test this a bit (albeit using some random value for _something
to stop it from giving us errors), we find that it works for all binary trees. As mentioned though, we still need to figure out that _something
value. This value will only matter in two cases: empty trees (in which case it will trivially match EmptyT
), and trees with only one subtree (in which case either l
or r
will match EmptyT
). I will leave it as an exercise for you to determine what value to put there, how it will affect the result, and why it affects it that way.
回答2:
We can derive and use Foldable
, to fold into an ad-hoc monoid to do the job:
data Tree a = EmptyT
| NodeT a ( Tree a ) ( Tree a )
deriving (Show, Functor, Foldable)
data T a = T a -- tip
| N [[a]] -- node
| TN (a,[[a]]) -- tip <> node
| NN ([[a]],[[a]]) -- node <> node
deriving Show
instance Monoid (T a) where
mempty = N [] -- (tip <> node <> node) is what we actually want
mappend (T a) (N as) = TN (a,as) -- tip <> node
mappend (N as) (N bs) = NN (as,bs) -- node <> node
mappend (T a) (NN ([],[])) = N ([[a]]) -- tip <> (node <> node)
mappend (T a) (NN (as,bs)) = N (map (a:) as ++ map (a:) bs)
mappend (TN (a,[])) (N []) = N ([[a]]) -- (tip <> node) <> node
mappend (TN (a,as)) (N bs) = N (map (a:) as ++ map (a:) bs)
allPaths :: Tree a -> [[a]]
allPaths (foldMap T -> N ps) = ps
The allPaths
function definition uses ViewPatterns. Testing,
> allPaths $ NodeT 1 (NodeT 2 (NodeT 3 EmptyT EmptyT) EmptyT)
(NodeT 5 EmptyT EmptyT)
[[1,2,3],[1,5]]
> allPaths $ NodeT 1 (NodeT 2 (NodeT 3 EmptyT EmptyT) (NodeT 4 EmptyT EmptyT))
(NodeT 5 EmptyT EmptyT)
[[1,2,3],[1,2,4],[1,5]]
(tip <> node <> node)
is what we really want, but <>
is binary, and we don't know (and shouldn't rely on it if we did) the actual order in which the parts will be combined into the whole by the derived definition of foldMap
,
foldMap T EmptyT == N []
foldMap T (NodeT a lt rt) == T a <> foldMap T lt <> foldMap T rt
-- but in what order?
So we "fake", it by delaying the actual combination until all three parts are available.
Or we could forgo the derivation route altogether, use the above laws as the definition of a custom foldMap
with a ternary combination, and end up with ... the equivalent of the recursive code in the other answer -- much shorter overall, without the utilitarian cruft of one-off auxiliary types that need to be hidden behind module walls, and self-evidently non-partial, unlike what we've ended up with, here.
So maybe it's not so great. I'll post it anyway, as a counterpoint.
来源:https://stackoverflow.com/questions/55662192/building-a-list-of-all-branches-in-a-tree