我需要使函数从树中返回所有可能的分支 格式如下:
data Tree a = EmptyT | NodeT a ( Tree a ) ( Tree a ) deriving (Show)
everyBranch :: Tree a -> [[a]]
我不确定该如何处理... xD 我仍然是Haskell的新手。
让我说:
1
/ \
2 3
/\ / \
4 5 7 8
我想获得:[[1,2,4], [1,2,5], [1,3,8], [1,3,7]]
答案 0 :(得分:7)
我们将使用递归方法。让我们从一个粗略的骨架开始:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v (Tree l) (Tree r)) = _somethingElse
现在,我们将填补漏洞。 (此语法称为“类型化的孔”:如果您通过GHC运行上述程序,则会向您显示一条错误消息,其中应包含该孔中的值的类型。)现在,我不确定第一种情况:根据您的需要,它可能是[]
(无分支)或[[]]
(一个无元素的分支),所以我们稍后再讲。对于第二种情况,我们需要一种方法来构造给定v
alue和l
eft和r
ight子树的分支列表。我们该怎么做?我们将以递归方式找到l
分支树中的每个分支,以及r
分支树中的每个分支,然后将v
放在这两个分支之前:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v l r) = map (v:) $ everyBranch l ++ everyBranch r
现在,让我们回到EmptyT
。考虑一个非常简单的树:NodeT 1 EmptyT EmptyT
。在这种情况下,everyBranch
应该返回[[1]]
。让我们在此树上手动调用everyBranch
:
(我用└→
表示“递归评估子表达式”,而=>
则表示“表达式求值”)
everyBranch (NodeT 1 EmptyT EmptyT)
=> map (1:) $ everyBranch EmptyT ++ everyBranch EmptyT
└→ everyBranch EmptyT
=> _something
=> map (1:) $ _something ++ _something
所以在这里,我们希望map (1:) $ _something ++ _something
等于[[1]]
。什么是_something
?好吧,事实证明,如果_something
是[]
,那么map (1:) $ [] ++ []
是[]
,这不是我们想要的。另一方面,如果_something
为[[]]
,则map (1:) $ [[]] ++ [[]]
为[[1], [1]]
-这也不是我们想要的。看来我们需要稍微不同的方法。我们要做的是,我们将专门为此类树添加另一种情况:
everyBranch :: Tree a -> [[a]]
everyBranch EmptyT = _something
everyBranch (NodeT v EmptyT EmptyT) = [[v]]
everyBranch (NodeT v l r) = map (v:) $ everyBranch l ++ everyBranch r
现在,如果我们对此进行一点测试(尽管使用_something
的一些随机值来阻止它给我们带来错误),我们会发现它适用于所有二叉树。如前所述,我们仍然需要弄清楚_something
的值。该值仅在以下两种情况下才有意义:空树(在这种情况下它将与EmptyT
微妙地匹配)和仅具有一个子树的树(在这种情况下l
或r
将匹配EmptyT
)。我将把它留作练习,让您确定要放置的值,它将如何影响结果以及为什么会以这种方式影响结果。
答案 1 :(得分:0)
我们可以派生并使用Foldable
,将其折叠为临时的monoid以完成工作:
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
allPaths
函数定义使用ViewPatterns
。测试
> 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)
是我们真正想要的,但是<>
是二进制的,我们不知道(如果这样做的话,也不应该依赖它)零件的实际顺序通过foldMap
的派生定义组合成整体,
foldMap T EmptyT == N []
foldMap T (NodeT a lt rt) == T a <> foldMap T lt <> foldMap T rt
-- but in what order?
因此,我们通过延迟实际的组合直到所有三个部分都可用来“伪造”它。
或者我们可以完全放弃派生路线,将上述定律用作具有三元组合的自定义foldMap
的定义,最后得到...等于其他答案中的递归代码- -总的来说要短得多,而无需在模块壁后面隐藏一次性的辅助类型的功利性,而且显然不完全是非局部的,这与我们最终得到的结果不同。
所以也许不是很好。无论如何,我都会将其发布,作为对策。