【问题标题】:Fold / Recursion over Multiway Tree in f#f#中多路树的折叠/递归
【发布时间】:2013-06-01 18:20:17
【问题描述】:

我正在尝试调整 Brian 的二叉树折叠 (http://lorgonblog.wordpress.com/2008/04/06/catamorphisms-part-two/) 以申请多路树。

Brian 博客总结:

数据结构:

type Tree<'a> =  
    | Node of (*data*)'a * (*left*)Tree<'a> * (*right*)Tree<'a>  
    | Leaf 

let tree7 = Node(4, Node(2, Node(1, Leaf, Leaf), Node(3, Leaf, Leaf)),  
                    Node(6, Node(5, Leaf, Leaf), Node(7, Leaf, Leaf)))

二叉树折叠函数

let FoldTree nodeF leafV tree =   
    let rec Loop t cont =   
        match t with   
        | Node(x,left,right) -> Loop left  (fun lacc ->    
                                Loop right (fun racc ->   
                                cont (nodeF x lacc racc)))   
        | Leaf -> cont leafV   
    Loop tree (fun x -> x) 

例子

let SumNodes = FoldTree (fun x l r -> x + l + r) 0 tree7
let Tree6to0 = FoldTree (fun x l r -> Node((if x=6 then 0 else x), l, r)) Leaf tree7

Multiway Tree 版本 [不(完全)工作]

数据结构

type MultiTree = | MNode of int * list<MultiTree>

let Mtree7 = MNode(4, [MNode(2, [MNode(1,[]); MNode(3, [])]);  
                    MNode(6, [MNode(5, []); MNode(7, [])])])

折叠功能

let MFoldTree nodeF leafV tree = 
    let rec Loop  tree cont =   
        match tree with   
        | MNode(x,sub)::tail -> Loop (sub@tail) (fun acc -> cont(nodeF x acc))
        | [] -> cont leafV
    Loop  [tree] (fun x -> x) 

示例 1 返回 28 - 似乎有效

let MSumNodes = MFoldTree (fun x acc -> x + acc) 0 Mtree7

示例 2

不运行

let MTree6to0 = MFoldTree (fun x acc -> MNode((if x=6 then 0 else x), [acc])) Mtree7

最初我认为MFoldTree 在某个地方需要map.something,但我让它与@ 运算符一起工作。

对第二个示例的任何帮助和/或纠正我在 MFoldTree 函数中所做的事情都会很棒!

干杯

dusiod

【问题讨论】:

    标签: f# tree traversal fold multiway-tree


    【解决方案1】:

    诀窍是你需要传递一个额外的函数来折叠。

    在 Brian 的版本中,fold 函数只接受 nodeF 调用,该函数使用节点中的值和左右子树产生的两个值。

    这对于多路树来说是不够的。在这里,我们需要一个函数nodeF,它使用节点中的值和聚合子树的所有值产生的结果来调用。但是你还需要一个函数——比如combineF,它结合了一个节点的多个子树产生的值。

    您的 fold 函数是一个好的开始 - 您只需要再添加一个递归调用来处理 tail

    let MFoldTree nodeF combineF leafV tree = 
        let rec Loop trees cont =   
            match trees with   
            | MNode(x,sub)::tail -> 
                // First, process the sub-trees of the current node and get 
                // a single value called 'accSub' representing (aggregated)
                // folding of the sub-trees.
                Loop sub (fun accSub -> 
                  // Now we can call 'nodeF' on the current value & folded sub-tree
                  let resNode = nodeF x accSub
                  // But now we also need to fold all remaining trees that were
                  // passed to us in the parameter 'trees'..
                  Loop tail (fun accTail ->
                    // This produces a value 'accTail' and now we need to combine the
                    // result from the tail with the one for the first node 
                    // (which is where we need 'combineF')
                    cont(combineF resNode accTail) ))
            | [] -> cont leafV
        Loop  [tree] (fun x -> x) 
    

    求和很容易,因为我们只对这两个函数使用+ 运算符:

    let MSumNodes = MFoldTree (+) (+) 0 Mtree7
    

    过滤树更棘手。 nodeF 函数将获取节点中的元素和子节点的列表(即聚合的结果)并生成一个单个节点。 combineF 函数将从第一个节点(即 MultiTree 值)和从剩余节点生成的子节点列表中获取结果。从空树产生的初始值是一个空列表:

    let MTree6to0 = 
      MFoldTree (fun x children -> MNode((if x=6 then 0 else x), children)) 
                (fun head tail -> head::tail) [] Mtree7
    

    【讨论】:

    • 太棒了 - 你是 F# 传奇! =P 非常感谢!
    • 顺便说一句:当您第一次尝试编写没有延续的函数时,可能更容易理解问题。延续可以帮助您避免堆栈溢出,但对于合理大小的树来说,这通常不是问题。一旦你有了非延续版本,将它变成基于延续的版本并不难。
    • 试过了,甚至不想承认我花了多少时间试图做到这一点......现在大脑太痛苦了,无法逐步完成并正确理解你的解决方案,但它会得到一些适当注意 v.soon.再次感谢您的帮助!
    • @TomasPetricek 整个 CPS 在 Brian 的代码中的皱纹肯定让掌握它有点棘手。但是一旦你掌握了它,它就会成为你工具箱中非常有用的技术。
    • 有人可以添加类型签名吗?
    【解决方案2】:

    另一种解决方案可能是

    let rec mfold f a (MNode(x,s)) = f (List.fold (fun a t -> mfold f a t) a s) x
    

    真的,我们可以将树视为一个线性结构(折叠它)。

    用例

    > mfold (+) 0 Mtree7;;
    val it : int = 28
    

    过滤器和普通折叠一样(因为mfold是普通折叠):

    > mfold (fun a x -> if x = 6 then a else x + a) 0 Mtree7;;
    val it : int = 22
    

    该函数可以是泛型的(如 List.foldArray.fold、... 可以是泛型)。

    “但第二个的目的是返回修改后的整个树,以便任何具有值 6 的节点现在都具有值 0”

    但这不是fold 计算,而是map

    您可以轻松完成(再次将其视为线性结构)

    let rec mmap f (MNode(x,s)) = MNode(f x, List.map (mmap f) s)
    

    用例

    > mmap (fun x -> if x=6 then 0 else x) Mtree7;;
    val it : MultiTree =
      MNode
        (4,
         [MNode (2,[MNode (1,[]); MNode (3,[])]);
          MNode (0,[MNode (5,[]); MNode (7,[])])])
    

    再次,我建议为每个可能的列表容器(SeqListArray、...)执行此操作,它可以让用户在上下文中选择最佳策略。

    注意事项:

    • 我是 F# 新手,如有错误请见谅。
    • 堆栈大小应该不是问题,堆栈级别等于树的深度。

    【讨论】:

    • 嗨 josejuan,该解决方案适用于第一种情况,但第二种情况的目的是返回修改后的整个树,以便任何值为 6 的节点现在具有值 0。另一种选择示例 1 为:let MFoldTree2 tree = let rec Loop trees = match trees with | MNode(x,sub)::tail -&gt; x + (Loop sub) + (Loop tail) | [] -&gt; 0 Loop tree
    • @dusiod 不是fold!是map! :D(我已经更新了解决方案)
    猜你喜欢
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 2012-07-24
    • 1970-01-01
    • 2012-04-13
    • 1970-01-01
    • 2023-04-09
    相关资源
    最近更新 更多