I was asked to look into writing some simulation code for a user, where the simulation output (xx
) would oscillate between three possible states:
states[x_, \[Gamma]_]γ_] := Which[
x < -\[Gamma]γ, 1 (*top*),
x < \[Gamma]γ , -1 (*bottom*),
True , 0 (*middle*)]
The simulation essentially boiled down to the following, and what they were interested in is computing the lengths runs in each state - which could be easily computed with SplitBySplitBy
:
\[Gamma]γ = 0.9;
sampleSimulation = RandomChoice[{-1, 0, 1}, 1000000];
runLengths =
Switch[Sign[#[[1]]],
1, {"top", Length[#]},
-1, {"bottom", Length[#]},
0, {"middle", Length[#]}
] & /@
SplitBy[sampleSimulation, states[#, \[Gamma]]γ] &]
However, if the simulation output moved between the "top""top"
and "bottom""bottom"
states (in either direction) without occupying the "middle""middle"
state they needed this information in runLengthsrunLengths
via the string "unoccupied middle""unoccupied middle"
.
The following code achieves that and is surprisingly performant and the experimenter was happy. However it feels unnecessary to SplitBySplitBy
and then ReapReap
and SowSow
with a DoDo
loop to find where the simulation moves seemlessly between "top""top"
and "bottom""bottom"
. What would be a more natural, and less wasteful method to solve such a problem?
runLengthsFunc[list_, \[Gamma]_]γ_] :=
Block[{splitting},
splitting =
Switch[Sign[#[[1]]],
1, {"top", Length[#]}, -1, {"bottom", Length[#]},
0, {"middle", Length[#]}] & /@
SplitBy[list, states[#, \[Gamma]]γ] &];
Append[Flatten[
Reap[Do[If[(splitting[[k, 1]] == "top" &&
splitting[[k + 1, 1]] == "bottom") || (splitting[[k, 1]] ==
"bottom" && splitting[[k + 1, 1]] == "top"),
Sow[{splitting[[k]], "unoccupied middle"}],
Sow[{splitting[[k]]}]],
{k, Length[splitting] - 1}
]][[2]], 2], splitting[[-1]]]]
Timing:
In[10]:=AbsoluteTiming[runLengthsFunc[sampleSimulation, \[Gamma]];]γ];]
Out[10]:={7.526282, Null}