Question

I am trying break this cyclic dependency below but not sure the best way to do it.

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

Using some logic from this answer I came up with the following solution but the performance is really poor.

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 
*)

Is there anyway to rewrite the cashClosingBalance function to stop the excessive recursive calls as seen in the output below? I really need to be able to put in a value of t of up to 400 and it still run in seconds.

Était-ce utile?

La solution

The problem isn't really in the fact that you have moreComplicatedLogic in the middle (and so it is inconvenient to write big let rec). The problem is that your code is implementing Dynamic Programming algorithm in an inefficient way.

The recursive calls end up calling cashClosingBalance multiple times with the same argument (rather than calling it just once and storing the result in some local cache). In functional programming you can solve this using a fairly general concept of memoization but you might be able to rewrite your algorithm differently to make it more efficient.

If you want to use memoization, then you need something like this - the following helper takes a function and creates a function of the same type which caches the results of previous calls:

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

Then you can rewrite your code using memoize like this (I simply wrapped all function definitions in memoize and changed the order of arguments so that t is the last one):

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top