Haskell 哈斯凯尔:我如何反转递归树的子树
我正试图扭转哈斯克尔一棵树上的孩子们。这棵树看起来像这样:Haskell 哈斯凯尔:我如何反转递归树的子树,haskell,recursion,tree,Haskell,Recursion,Tree,我正试图扭转哈斯克尔一棵树上的孩子们。这棵树看起来像这样: data Tree v e = Node v [(e,Tree v e)] 目前,我使用以下两个功能: rev :: Tree v e -> Tree v e rev (Node v []) = Node v [] rev (Node v xs) = let xs' = (rev'(snd (unzip xs))) in Node v (zip (fst (unzip xs)) (map rev xs')) re
data Tree v e = Node v [(e,Tree v e)]
目前,我使用以下两个功能:
rev :: Tree v e -> Tree v e
rev (Node v []) = Node v []
rev (Node v xs) =
let xs' = (rev'(snd (unzip xs)))
in Node v (zip (fst (unzip xs)) (map rev xs'))
rev' :: [Tree v e] -> [Tree v e]
rev' [] = []
rev' (x:xs) = (rev' xs) ++ [x]
我甚至不确定这是否100%有效。有没有什么方法可以在一个函数中实现递归呢?我觉得我的方式效率很低。我试图编写一个如下所示的函数:
revRec :: Tree v e -> Tree v e
revRec (Node v []) = Node v []
revRec (Node v (x:xs)) = Node v (revRec xs ++ [x])
显然,我不能用
xs
调用revRec
,因为xs
是一个列表。尽管我觉得这个问题不应该那么难,但我还是不能把我的头脑集中在这个问题上。Zeroth,清理你的代码。你不需要那么多参数,你应该避免重复计算<代码>fst和snd
在同一件事上两次?无聊的。更好地使用模式匹配:
rev :: Tree v e -> Tree v e
rev (Node v xs)
= let (es, cs) = unzip xs
xs' = rev' cs
in Node v $ zip es (map rev xs')
rev' :: [Tree v e] -> [Tree v e]
rev' [] = []
rev' (x:xs) = rev' xs ++ [x]
首先请注意,rev'
根本不是特定于树列表的!这只是标准函数的一个低效实现,它适用于任何列表
第二,这个解压有点笨拙。你确定这就是你想要的行为吗:现在你只是在反转子元素列表,但是元素的顺序保持不变。如果没有,则最好使用一个反转和一张地图:
rev :: Tree v e -> Tree v e
rev (Node v xs) = Node v . reverse $ map (\(e,c) -> (e, rev c)) xs
或者,甚至更好
import Control.Arrow
rev (Node v xs) = Node v . reverse $ map (second rev) xs
Zeroth,清理代码。你不需要那么多参数,你应该避免重复计算<代码>fst和
snd
在同一件事上两次?无聊的。更好地使用模式匹配:
rev :: Tree v e -> Tree v e
rev (Node v xs)
= let (es, cs) = unzip xs
xs' = rev' cs
in Node v $ zip es (map rev xs')
rev' :: [Tree v e] -> [Tree v e]
rev' [] = []
rev' (x:xs) = rev' xs ++ [x]
首先请注意,rev'
根本不是特定于树列表的!这只是标准函数的一个低效实现,它适用于任何列表
第二,这个解压有点笨拙。你确定这就是你想要的行为吗:现在你只是在反转子元素列表,但是元素的顺序保持不变。如果没有,则最好使用一个反转和一张地图:
rev :: Tree v e -> Tree v e
rev (Node v xs) = Node v . reverse $ map (\(e,c) -> (e, rev c)) xs
或者,甚至更好
import Control.Arrow
rev (Node v xs) = Node v . reverse $ map (second rev) xs
首先,您甚至不需要
rev(Node v[])=代码>案例,常规列表案例也包括空列表案例。其次,使用unzip
似乎非常合理-可能应该取消使用fst
和snd
(也可以多次调用unzip
)。您可以使用原语递归编写它,例如:rev(Node a ts)=Node a$uncurry(\x y->zip x(y[]))$foldr(\(e,t)(as,bs)->(e:as,bs.(rev t:))([],id)ts
。但我真的怀疑这是否比解压/反转版本更有效(当然更难看)代码>案例,常规列表案例也包括空列表案例。其次,使用unzip
似乎非常合理-可能应该取消使用fst
和snd
(也可以多次调用unzip
)。您可以使用原语递归编写它,例如:rev(Node a ts)=Node a$uncurry(\x y->zip x(y[]))$foldr(\(e,t)(as,bs)->(e:as,bs.(rev t:))([],id)ts
。但我真的怀疑这是否比解压/反转版本更有效(而且肯定更难看)。一个挑剔的效率点:出于充分的理由(真的,我保证),反转
不参与列表融合。因此,相反。map f
理想情况下应该手工融合到mapReverse
的应用程序中(这是我编的一个名字,意义很明显)。一个挑剔的说教要点是:箭头
抽象对我这样的中级Haskeller来说都是可怕的。因此,我强烈建议使用Data.Bifunctor
中的第二个<在我看来,code>Bifunctor
是一个对初学者更为友好的抽象概念。对于(,)
,只使用Functor实例怎么样?如果我没记错的话,它与第二个相同。@epsilonhalbe,它实际上不一样,因为函子
实例是严格守法的(因此在这里可能更好),而双函子
实例只是道德上如此。所以你可能是对的,fmap
更好。这就是说,(,)
比(a,)
是一个函子更直观的双functor
。@dfeuer我不同意,箭头是一个很好的抽象。(作为单子的替代品,它们很棘手——而且不必要,但我不认为Arrow
类本身有什么难以理解的地方——你知道,它只是一个很好的单项式范畴,有什么问题……)Wheras@epsilonhalbe是的函子实例(a,)
和a->
都是,嗯……我不是这些的粉丝。真的,(,)
是一个典型的双功能的例子,看“一半双功能”是一个非常片面的观点。一个挑剔的效率点:有充分的理由(真的,我保证),reverse
不参与列表融合。因此,理想情况下,reverse.map f
应该手工融合到mapReverse
的应用程序中(一个我自己编的名字,意义很明显)一个很挑剔的说教点:Arrow
抽象对像我这样的中级Haskeller来说都是很吓人的。因此,我强烈建议使用Data.Bifunctor
中的second
。Bifunctor
在我看来是一个更为初学者友好的抽象或者(,)
?如果我没记错的话,它与秒
是一样的。@epsilonhalbe,它实际上不一样,因为函子
实例严格遵守法律(因此这里可能更好)而Bifunctor
的例子只是在道德上如此。所以你可能是对的,fmap
更好。也就是说,(,)
bein是有理由的