f# - 结合内存和尾递归

标签 f# functional-programming tail-recursion memoization

是否有可能以某种方式结合内存和尾递归?我目前正在学习 F# 并理解这两个概念,但似乎无法将它们结合起来。

假设我有以下 memoize函数(来自 Real-World Functional Programming):

let memoize f = let cache = new Dictionary<_, _>()
                (fun x -> match cache.TryGetValue(x) with
                          | true, y -> y
                          | _       -> let v = f(x)
                                       cache.Add(x, v)
                                       v)

和以下 factorial功能:
let rec factorial(x) = if (x = 0) then 1 else x * factorial(x - 1)

备忘 factorial并不太难,使其尾递归也不是:
let rec memoizedFactorial =
  memoize (fun x -> if (x = 0) then 1 else x * memoizedFactorial(x - 1))

let tailRecursiveFactorial(x) =
  let rec factorialUtil(x, res) = if (x = 0)
                                  then res
                                  else let newRes = x * res
                                       factorialUtil(x - 1, newRes)
  factorialUtil(x, 1)

但是你能把内存和尾递归结合起来吗?我做了一些尝试,但似乎无法让它工作。或者这根本不可能?

最佳答案

与往常一样,延续会产生一个优雅的尾调用解决方案:

open System.Collections.Generic 

let cache = Dictionary<_,_>()  // TODO move inside 
let memoizedTRFactorial =
    let rec fac n k =  // must make tailcalls to k
        match cache.TryGetValue(n) with
        | true, r -> k r
        | _ -> 
            if n=0 then
                k 1
            else
                fac (n-1) (fun r1 ->
                    printfn "multiplying by %d" n  //***
                    let r = r1 * n
                    cache.Add(n,r)
                    k r)
    fun n -> fac n id

printfn "---"
let r = memoizedTRFactorial 4
printfn "%d" r
for KeyValue(k,v) in cache do
    printfn "%d: %d" k v

printfn "---"
let r2 = memoizedTRFactorial 5
printfn "%d" r2

printfn "---"

// comment out *** line, then run this
//let r3 = memoizedTRFactorial 100000
//printfn "%d" r3

有两种测试。首先,这个演示调用 F(4) 可以根据需要缓存 F(4)、F(3)、F(2)、F(1)。

然后,注释掉 *** printf 并取消注释最终测试(并在 Release模式下编译)以表明它没有 StackOverflow(它正确使用尾调用)。

也许我会概括出'memoize'并在接下来的'fib'上演示它......

编辑

好的,我认为这是下一步,将记忆化与阶乘分离:
open System.Collections.Generic 

let cache = Dictionary<_,_>()  // TODO move inside 
let memoize fGuts n =
    let rec newFunc n k =  // must make tailcalls to k
        match cache.TryGetValue(n) with
        | true, r -> k r
        | _ -> 
            fGuts n (fun r ->
                        cache.Add(n,r)
                        k r) newFunc
    newFunc n id 
let TRFactorialGuts n k memoGuts =
    if n=0 then
        k 1
    else
        memoGuts (n-1) (fun r1 ->
            printfn "multiplying by %d" n  //***
            let r = r1 * n
            k r) 

let memoizedTRFactorial = memoize TRFactorialGuts 

printfn "---"
let r = memoizedTRFactorial 4
printfn "%d" r
for KeyValue(k,v) in cache do
    printfn "%d: %d" k v

printfn "---"
let r2 = memoizedTRFactorial 5
printfn "%d" r2

printfn "---"

// comment out *** line, then run this
//let r3 = memoizedTRFactorial 100000
//printfn "%d" r3

编辑

好的,这是一个完全通用的版本,似乎可行。
open System.Collections.Generic 

let memoize fGuts =
    let cache = Dictionary<_,_>()
    let rec newFunc n k =  // must make tailcalls to k
        match cache.TryGetValue(n) with
        | true, r -> k r
        | _ -> 
            fGuts n (fun r ->
                        cache.Add(n,r)
                        k r) newFunc
    cache, (fun n -> newFunc n id)
let TRFactorialGuts n k memoGuts =
    if n=0 then
        k 1
    else
        memoGuts (n-1) (fun r1 ->
            printfn "multiplying by %d" n  //***
            let r = r1 * n
            k r) 

let facCache,memoizedTRFactorial = memoize TRFactorialGuts 

printfn "---"
let r = memoizedTRFactorial 4
printfn "%d" r
for KeyValue(k,v) in facCache do
    printfn "%d: %d" k v

printfn "---"
let r2 = memoizedTRFactorial 5
printfn "%d" r2

printfn "---"

// comment out *** line, then run this
//let r3 = memoizedTRFactorial 100000
//printfn "%d" r3

let TRFibGuts n k memoGuts =
    if n=0 || n=1 then
        k 1
    else
        memoGuts (n-1) (fun r1 ->
            memoGuts (n-2) (fun r2 ->
                printfn "adding %d+%d" r1 r2 //%%%
                let r = r1+r2
                k r)) 
let fibCache, memoizedTRFib = memoize TRFibGuts 
printfn "---"
let r5 = memoizedTRFib 4
printfn "%d" r5
for KeyValue(k,v) in fibCache do
    printfn "%d: %d" k v

printfn "---"
let r6 = memoizedTRFib 5
printfn "%d" r6

printfn "---"

// comment out %%% line, then run this
//let r7 = memoizedTRFib 100000
//printfn "%d" r7

关于f# - 结合内存和尾递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3459422/

相关文章:

visual-studio-2010 - 将“nunit-console”输出重定向到Visual Studio输出窗口

function - F#:递归函数:连接两个列表之间的公共(public)值

algorithm - 将线性递归函数重写为尾递归函数

f# - 为什么我的应用程序从上到下执行

f# - 使用异步工作流进行并行化的最佳实践

f# - 如何使用 AutoMapper 将具有多个泛型列表的对象映射到具有相应非泛型列表的另一个对象?

list - 用 Foldl 剪掉任何内容

functional-programming - 如何在 Clojure 中返回递归函数的输出

oop - 如何在函数式编程中建模继承关系

scala - 递归处理scala中的嵌套列表