【问题标题】:F# tail call optimization with 2 recursive calls?带有 2 个递归调用的 F# 尾调用优化?
【发布时间】:2011-09-13 03:36:14
【问题描述】:

当我编写这个函数时,我知道我不会得到尾调用优化。我还没有想出处理这个问题的好方法,希望其他人可以提供建议。

我有一棵树:

type Heap<'a> =
| E
| T of int * 'a * Heap<'a> * Heap<'a> 

我想计算其中有多少个节点:

let count h =
    let rec count' h acc =
        match h with 
        | E -> 0 + acc
        | T(_, value, leftChild, rightChild) ->
            let acc = 1 + acc
            (count' leftChild acc) + (count' rightChild acc)

    count' h 0

这不是因为添加了子节点的计数而没有优化。如果树有 100 万个节点,你知道如何让这样的事情工作吗?

谢谢,德里克


这里是使用 CPS 实现计数。尽管如此,它还是把堆栈搞砸了。

let count h =
    let rec count' h acc cont =
        match h with
        | E -> cont (1 + acc)
        | T(_,_,left,right) ->
            let f = (fun lc -> count' right lc cont)
            count' left acc f

    count' h 0 (fun (x: int) -> x)

也许我可以想出一些方法来将树分成足够多的块,这样我就可以在不破坏堆栈的情况下进行计数?

有人询问生成树的代码。它在下面。

member this.ParallelHeaps threads =
    let rand = new Random()
    let maxVal = 1000000

    let rec heaper i h =
        if i < 1 then
            h
        else
            let heap = LeftistHeap.insert (rand.Next(100,2 * maxVal)) h
            heaper (i - 1) heap

    let heaps = Array.create threads E
    printfn "Creating heap of %d elements, with %d threads" maxVal threads
    let startTime = DateTime.Now
    seq { for i in 0 .. (threads - 1) ->
          async { Array.set heaps i (heaper (maxVal / threads) E) }}
    |> Async.Parallel
    |> Async.RunSynchronously 
    |> ignore

    printfn "Creating %d sub-heaps took %f milliseconds" threads (DateTime.Now - startTime).TotalMilliseconds
    let startTime = DateTime.Now

    Array.length heaps |> should_ equal threads <| "The size of the heaps array should match the number of threads to process the heaps"

    let rec reMerge i h =
        match i with 
        | -1 -> h
        | _  -> 
            printfn "heap[%d].count = %d" i (LeftistHeap.count heaps.[i])
            LeftistHeap.merge heaps.[i] (reMerge (i-1) h)

    let heap = reMerge (threads-1) E
    printfn "Merging %d heaps took %f milliseconds" threads (DateTime.Now - startTime).TotalMilliseconds
    printfn "heap min: %d" (LeftistHeap.findMin heap)

    LeftistHeap.count heap |> should_ equal maxVal <| "The count of the reMerged heap should equal maxVal"

【问题讨论】:

    标签: recursion f# functional-programming tail-recursion tail-call-optimization


    【解决方案1】:

    您可以使用延续传递样式 (CPS) 来解决该问题。请参阅 Matthew Podwysocki 的 Recursing on Recursion - Continuation Passing

    let tree_size_cont tree = 
      let rec size_acc tree acc cont = 
        match tree with 
        | Leaf _ -> cont (1 + acc) 
        | Node(_, left, right) -> 
             size_acc left acc (fun left_size -> 
             size_acc right left_size cont) 
    
      size_acc tree 0 (fun x -> x)
    

    另请注意,在调试版本中,尾调用优化被禁用。如果不想在 Release 模式下运行,可以在 Visual Studio 的项目属性中启用优化。

    【讨论】:

    • 在内存最终耗尽之前,延续函数不会增长吗?
    • 一般来说,是的,但这被认为是可以的。然而,据我所知,对于大多数语言,包括 F#,堆栈都有一个静态大小(在加载或编译时设置),因此它会比使用 CPS 创建的内存不足情况更快地创建堆栈溢出。一般来说,许多计算问题需要无限数据结构来处理任意输入,无论您是通过显式数据结构还是像不断增长的延续这样的“隐式”数据结构来做到这一点都是任意的:数据需要去某个地方。
    • 我之前曾想过尝试这样做,但说服自己放弃了它,因为我认为它仍然会破坏堆栈。在 Joh 建议之后,我尝试了一下,希望我错了。但是不,count 函数仍然会破坏堆栈。这是我的实现......我不能在这里做到这一点,让我尝试另一个答案。您如何将代码发布到回复中?
    • ` let count h = let rec count' h acc cont = match h with | E -> 续 (1 + acc) | T(,,left,right) -> let f = (fun lc -> count' right lc cont) count' left acc f count' h 0 (fun (x: int) -> x )`
    • 有人指出,如果代码是在调试模式下构建的,tailcall 优化是禁用的。一旦我在 Release 模式下重建,我就能数出一棵有 100 万个节点的树,甜!感谢您指出@kvb。
    【解决方案2】:

    CPS 是一个很好的通用解决方案,但您可能还想考虑显式使用堆栈,因为它会更快并且可以说更简单:

    let count heap =
      let stack = System.Collections.Generic.Stack[heap]
      let mutable n = 0
      while stack.Count > 0 do
        match stack.Pop() with
        | E -> ()
        | T(_, _, heap1, heap2) ->
            n <- n + 1
            stack.Push heap1
            stack.Push heap2
      n
    

    【讨论】:

    • Stack[heap] 是什么意思?
    • 从给定的序列构造一个Stack 集合,一个包含单个元素(值heap)的列表。
    猜你喜欢
    • 2021-05-30
    • 1970-01-01
    • 2018-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多