Building a list of all branches in a tree

南楼画角 提交于 2020-08-26 10:03:58

问题


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 value and the left and right subtrees. How do we do that? We'll recursively find every branch in the left tree, and every branch in the right 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!