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