4

I am working on the following code:

f[1] = Cos[t];
f[2] = Sin[t];
f[3] = 1.2 Cos[1.2 Pi t];
f[4] = 1.2 Sin[1.2 Pi t];
f[5] = 1.7 Cos[1.7 Pi t];
f[6] = 1.7 Sin[1.7 Pi t];
u = 0;
s = 0.1;

o[n_] := (o[n] = Table[{f[2 n - 1], f[2 n]}, {t, 0, 2 Pi, s }]; u++)

o[1]; o[2]; o[3]; u

If I run it, it outputs $u=3$ correctly. But if I run it again, it outputs $u=0$. What is going on here? I understand that $u$ is defined as $0$ in the beginning of the code, but shouldn't re-running it increment $u$ three times again and $u$ should be $3$?

How can I force $u$ to be incremented when calling o[1] after o[1] has been defined? I know I could use $\text{Clear["Global`*"]}$ in the beginning of the code, but it would be nice to use the memoization in there too.

Red Banana
  • 5,329
  • 2
  • 29
  • 47
  • 1
    Because the code for o[1], o[2], o[3] is different after memoization. You can check with ? o or Downvalues[o]. – Michael E2 Dec 05 '21 at 20:44
  • @MichaelE2 Can't I force o[i] it to increment everytime, even when it is declared? I am trying o[n_] := (o[n] = (u++; Table[{f[2 n - 1], f[2 n]}, {t, 0, 2 Pi, s }])) but this is also not working. – Red Banana Dec 05 '21 at 20:53
  • 1
    Update your question to be how to do that. I think it's ok to change it at this point, since no one has answered (and the answer to the current one is rather simple). How to do it is a more interesting question, anyway. – Michael E2 Dec 05 '21 at 20:56
  • @MichaelE2 Done. I wasn't too sure about what to ask so I guess this is could come up with. – Red Banana Dec 05 '21 at 20:59
  • 1
    I thought you would add "How can I memoize the result of o[i] and at the same time force u to be incremented whenever o[i] is called?" (Unless I've misunderstood what your question is.) – Michael E2 Dec 05 '21 at 21:02
  • @MichaelE2 I think you understood correctly. – Red Banana Dec 05 '21 at 21:04

2 Answers2

5

Introducing a new memoized variable,om, allows it to be done like this

u = 0;
Clear[o, om]
om[n_] := (Pause[1]; 
  om[n] = Table[{f[2 n - 1], f[2 n]}, {t, 0, 2 Pi, s}])
o[n_] := (u++; om[n])

First@AbsoluteTiming@o /@ {1, 2, 1, 2} (* {1.00142, 1.56995, 3.10^-6, 2.10^-6} ) u ( 4 *)

The Pause is there only to make the first evaluation expensive. AbsoluteTiming shows the expensive evaluation is performed the first time and the memoized value is returned thereafter. And the counter works.

LouisB
  • 12,528
  • 1
  • 21
  • 31
4

Memoization, as I understand it (I'm not broadly versed in programming concepts), usually redefines the specific function call to be the computed output value. In Mathematica, we can make any definition we like, including adding side effects to a memoized call:

mem : o[n_] := 
  With[{res = Table[{f[2 n - 1], f[2 n]}, {t, 0, 2 Pi, s}]},
   mem := ( (* define the memoized actions *)
     u++;
     (* other code if desired; *)
     res); (* return value last *)
   mem
   ];

Notes: mem represents the call, for instance o[1]. We have to use SetDelayed to make the new definition for mem (or o[1] etc.). Also, the side effect u++ should occur on the first call, so the return value of o[n_] comes from calling mem after it has be defined.

Example:

u = 0;
o[1];
o[2];
o[3];
u
(* 3  *)

o[1]; u (* 4 *)

Michael E2
  • 235,386
  • 17
  • 334
  • 747