如何实现F#中简单Rosetree的尾递归map函数?
尾递归玫瑰树map实现思路
尾递归实现的核心是把所有递归调用作为函数执行的最后一步,所有未完成的计算都打包成续体(continuation)向下传递,避免执行栈累积。你已经完成了Leaf分支的逻辑,Branch分支的难点在于需要批量处理子树列表,我们可以新增一个子树处理的尾递归辅助逻辑来解决:
完整实现代码
type Tree<'a> = | Leaf of 'a | Branch of List<Tree<'a>> let tailRecMap : ('a -> 'b) -> Tree<'a> -> Tree<'b> = fun f tree -> let rec innerMap : Tree<'a> -> (Tree<'b> -> 'r) -> 'r = fun tree cont -> match tree with | Leaf a -> // Leaf分支和你写的逻辑一致,映射后传给续体 cont (Leaf (f a)) | Branch children -> // 新增尾递归辅助函数处理子树列表 let rec processChildren remaining processed cont' = match remaining with | [] -> // 所有子树处理完成,反转顺序后包装为Branch传给外层续体 cont' (Branch (List.rev processed)) | t::rest -> // 先处理当前子树,续体逻辑为:子树处理完成后加入已处理列表,继续处理剩余子树 innerMap t (fun mappedT -> processChildren rest (mappedT::processed) cont') // 启动子树处理流程,最终结果传给外层续体 processChildren children [] cont // 初始续体用id,直接返回最终结果 innerMap tree id
逻辑说明
innerMap的返回值用泛型'r而不是固定的Tree<'b>,是为了适配续体的任意返回需求,这是通用CPS(续体传递风格)的标准写法- 处理
Branch时的processChildren本身也是尾递归:每次仅处理一个子树,递归调用processChildren是函数的最后一步 - 子树处理时每次把映射完成的节点加到已处理列表头部,最后统一反转,是为了避免列表追加的O(n)开销,整体时间复杂度和非尾递归版本一致,都是O(n)(n为树的总节点数)
测试示例
// 构造测试树:Branch [Leaf 1; Branch [Leaf 2; Leaf 3]; Leaf 4] let testTree = Branch [Leaf 1; Branch [Leaf 2; Leaf 3]; Leaf 4] // 执行映射:所有值加1 let mappedTree = tailRecMap ((+)1) testTree // 输出结果:Branch [Leaf 2; Branch [Leaf 3; Leaf 4]; Leaf 5]
如果需要改造bind函数,思路完全一致:只需要把Leaf分支的逻辑换成bind对应的规则,子树处理逻辑可以直接复用。
内容的提问来源于stack exchange,提问作者MrD at KookerellaLtd
相关产品推荐
相关产品推荐

