I am also going to ignore the "association" aspect of this for the sake of simplicity.
There is nothing wrong with loops conceptually; they just need to be written using the right Mathematica tools if one wants efficiency. (Unless you wish to write the entire thing in a procedural style and compile to C for maximum performance.) In this case FoldList
is probably the right tool. At each step we need to input the daily balance value, and we need to keep track of:
A running adjustment to balance
A daily deposit amount
Therefore I will start a function definition with: f[{adj_, dep_}, bal_] :=
. The parameter dep
is not actually used in the function; it is merely a value to carry along with the adjustment. My function:
f[{adj_, dep_}, bal_] :=
With[{x = adj + bal},
If[x >= 0, {adj, 0}, {adj - x, -x}]
]
Now:
balance = {20, -80, -70, 20, -100};
FoldList[f, {0, 0}, balance]
{{0, 0}, {0, 0}, {80, 80}, {80, 0}, {80, 0}, {100, 20}}
In FoldList
the {0, 0}
represents starting values for adj
and dep
that we will track. The output is of the form {adj, dep}. To get your deposit list we merely need to add Part
:
FoldList[f, {0, 0}, balance][[2 ;;, 2]]
{0, 80, 0, 0, 20}
If performance is a priority we should rewrite f
in a way that is compilable:
f2 =
Compile[{{track, _Real, 1}, {bal, _Real}},
Module[{x = track[[1]] + bal},
If[x >= 0, {track[[1]], 0}, {track[[1]] - x, -x}]
]
];
Comparison:
big = RandomReal[{-99, 99}, 1*^6];
FoldList[f, {0, 0}, big][[2 ;;, 2]] // AbsoluteTiming // First
FoldList[f2, {0`, 0`}, big][[2 ;;, 2]] // AbsoluteTiming // First
2.543146
0.434025
Note that because f2
is compiled to work with Reals I need to using a starting expression of {0`, 0`}
to achieve best performance; I missed this at first.
In the example above I used a two term List to hold the two tracked values. This is a generalizable method. However in this case we only care to keep one of these values in the ultimate output, and further we are not actually using this value in subsequent steps. We can therefore use a Symbol assignment and replace FoldList
with Map
:
fx[bal_] := With[{x = -adj - bal}, If[x < 0, 0, (adj += x; x)]]
adj = 0;
fx /@ balance
{0, 80, 0, 0, 20}
This is a mixed rather than functional style but it is still a useful technique to know. If you use it take precaution to avoid bugs related to global assignments. For example you should make the entire thing a function and adj
a scoped Symbol in Module
.
Freely borrowing from Simon's answer, and acknowledging the same performance limitations, one may also do this:
h[{a_, r___}, p_: {}] := h[{r} + #, Append[p, #]] & @ Max[-a, 0]
h[{}, p_] := p
Test:
h[balance]
{0, 80, 0, 0, 20}