【问题标题】:Cyclic dependency between functions in F#F#中函数之间的循环依赖
【发布时间】:2013-09-30 09:32:43
【问题描述】:

我正在尝试打破下面的这种循环依赖,但不确定最好的方法。

let cashOpeningBalance t =
if t = 1 then
    0.0
else
    cashClosingBalance (t - 1)

let cashInterest t = 
    cashOpeningBalance t * 0.03 

let accumulatedCash t =
    cashOpeningBalance t + cashInterest t

// let moreComplicatedLogic t = ...

let cashClosingBalance t =
    accumulatedCash t

使用answer 中的一些逻辑,我想出了以下解决方案,但性能确实很差。

let cashOpeningBalance t cashClosingBalance =
if t = 1 then
    10.0
else
    cashClosingBalance (t - 1)

let cashInterest t cashClosingBalance = 
    (cashOpeningBalance t cashClosingBalance) * 0.03 

let accumulatedCash t cashClosingBalance =
    (cashOpeningBalance t cashClosingBalance) + (cashInterest t cashClosingBalance)

// let moreComplicatedLogic t cashClosingBalance = ...

let rec cashClosingBalance t =
    //accumulatedCash t cashClosingBalance
    let temp = accumulatedCash t cashClosingBalance
    printfn "Cash Closing Balance = %f Where t = %i" temp t
    temp

cashClosingBalance 3
(*
> 
Cash Closing Balance = 10.300000 Where t = 1
Cash Closing Balance = 10.300000 Where t = 1
Cash Closing Balance = 10.609000 Where t = 2
Cash Closing Balance = 10.300000 Where t = 1
Cash Closing Balance = 10.300000 Where t = 1
Cash Closing Balance = 10.609000 Where t = 2
Cash Closing Balance = 10.927270 Where t = 3
val it : float = 10.92727
*)

cashClosingBalance 50
(*
Takes a really long time 
*)

是否有重写 cashClosingBalance 函数来停止过多的递归调用,如下面的输出所示?我真的需要能够输入高达 400 的 t 值,并且它仍然可以在几秒钟内运行。

【问题讨论】:

    标签: recursion f# cyclic-dependency


    【解决方案1】:

    问题实际上并不在于您在中间有moreComplicatedLogic(因此写大let rec 很不方便)。问题是您的代码以低效的方式实现Dynamic Programming algorithm

    递归调用最终会使用相同的参数多次调用cashClosingBalance(而不是只调用一次并将结果存储在某个本地缓存中)。在函数式编程中,您可以使用一个相当普遍的memoization 概念来解决这个问题,但您可能能够以不同的方式重写您的算法以提高效率。

    如果你想使用 memoization,那么你需要这样的东西 - 下面的帮助程序接受一个函数并创建一个相同类型的函数来缓存以前调用的结果:

    let memoize f = 
      let dict = System.Collections.Generic.Dictionary<_, _>() 
      fun v ->
        match dict.TryGetValue(v) with
        | true, res -> res
        | _ -> 
            let res = f v 
            dict.Add(v, res)
            res
    

    然后您可以像这样使用memoize 重写您的代码(我只是将所有函数定义包装在memoize 中并更改了参数的顺序,以便t 是最后一个):

    let cashOpeningBalance cashClosingBalance = memoize (fun t ->
      if t = 1 then
        10.0
      else
        cashClosingBalance (t - 1))
    
    let cashInterest cashClosingBalance = memoize (fun t -> 
        (cashOpeningBalance cashClosingBalance t) * 0.03 )
    
    let accumulatedCash cashClosingBalance = memoize (fun t ->
        (cashOpeningBalance cashClosingBalance t) + (cashInterest cashClosingBalance t))
    
    // let moreComplicatedLogic t cashClosingBalance = ...
    
    let rec cashClosingBalance = memoize (fun t -> 
        //accumulatedCash t cashClosingBalance
        let temp = accumulatedCash cashClosingBalance t
        printfn "Cash Closing Balance = %f Where t = %i" temp t
        temp)
    

    【讨论】:

    • 感谢这完美的作品。我喜欢 memoize 函数不会改变程序底层结构的方式!
    • 以前没见过这种设计策略。这似乎是一种动态创建查找表的巧妙方法。 添加到工具箱
    猜你喜欢
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 2019-02-04
    • 2014-11-10
    • 2021-04-23
    相关资源
    最近更新 更多