Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

This question mentions "x := x = trickery".

What does defining a function as f[x_] := f[x] = ... do and what is it good for?

share|improve this question
8  
You can also search for "memoization" both here and on SO (StackOverflow) Mathematica tag, to get many examples where this construct is used. –  Leonid Shifrin Mar 6 '12 at 16:26
6  
In short, it means "when x is called, evaluate x = ...". This way not only do you get x back but you also store the value. If you do only "x := ..." then x will remain unassigned. –  Timo Mar 6 '12 at 18:51

5 Answers 5

It's ... oh, why not let the docs speak:

tutorial/FunctionsThatRememberValuesTheyHaveFound

(in Doc center)

Edit

You may also find additional information by searching for "memoization" on this site. This has always been a great trick to avoid having to re-evaluate the result of a computationally intensive function call. In the above link, it is also used to do recursion.

I'll try to explain things without the added complication of recursion: The basic idea is that when you define a function using

f[x_]:= x^2

you are using x as the name of a pattern playing the role of a dummy variable. The := (meaning SetDelayed) has the property of leaving whatever is on the right-hand side unevaluated until it's needed, and this happens when a pattern matching the left side is encountered, say, f[4].

To make Mathematica remember things, you can't assign a value to a pattern, so you would normally say

f[4]=16

Now such definitions are found by Mathematica before it looks for matching pattern definitions such as f[x_]:=x^2 above. So in other words, if I type f[4] after having executed the above two lines, my "function definition" doesn't actually have to be used at all because the system already knows the result for the specific value 4.

The memoization trick now combines the above lines, which would lead to

f[x_]:=f[x]=x^2

The right-hand side of SetDelayed is now telling us to take whatever was passed in through the dummy variable x and assign this to f[x] using Set (the = sign). The result of that last operation is that a "non-pattern" f[x] with a specific new value of x has been defined for later use, and the value that got assigned to that is also returned as the replacement for the pattern f[x_] (i.e., the function value in the initial function call).

Whenever a new x is passed to f[x], we now get a new "permanent" definition added to the memory so that the function (the pattern) doesn't need to be evaluated again for that x.

It's best to play around with this yourself by defining a function along the lines above, and then periodically checking what Mathematica knows about your function by typing ?f.

share|improve this answer

It is a simple way to implement Memoization. The trick is that if you define a function as

f[x_]:=f[x]=ExpensiveFunctionOf[x]

then when you for the first time call e.g. f[3], it will evaluate as

f[3]=ExpensiveFunctionOf[3]

which will evalulate the expensive function, and assign the result to f[3] (in addition to giving it back, of course). So if e.g. ExpensiveFunctionOf[3] evaluates to 7, it will assign f[3]=7.

The next time you call f[3], it will find that newly created definition of f[3] and use that in preference to the general rule. This means it will return the value 7 without calculating ExpensiveFunctionOf[3] a second time.

share|improve this answer

It also has another practical side. If you have a random function, which

  • you only want to evaluate once, but
  • you don't know where exactly it will be evaluated or
  • you want to declare it before the parameters it depends on are defined, and
  • you want it to be the same after the first evaluation, for any subsequent call,

you can use the memoization trick:

initPopulation := initPopulation = InitializeRandomPopulation[args..];

(* lots of computation *)
(* the above *args* (on which the function depends!) are defined only now *)
(* and finally we got to the part where we compute the initial population *)

pop1 = initPopulation

(* use the very same initial population again and again... *)

pop2 = initPopulation 

...
share|improve this answer

Memoization is perhaps the most common application, but it is not the meaning of that construct.

More generally it is a construct for a function that redefines itself. This has many uses beyond memoization. Consider this function:

f[y_] := (f[y] = Sequence[]; y)

It is used to remove duplicates in a list. When the function is first called with a particular argument (expression) it redefines itself, only for that argument, to Sequence[]. The next time it is applied to that expression it therefore resolves to Sequence[] (because the more specific definition has priority) and the expression is effectively removed. (Of course it must be reinitialized between lists unless you want to remove duplicates globally.)

f /@ {3, 5, 2, 3, 2, 4, 3}
{3, 5, 2, 4}

You can also write a function that changes its behavior after the first use.
Note the different pattern name to avoid conflict.

g[x_] := (g[y_] := y - 1; x + 1)

g /@ {1, 2, 3}
{2, 1, 2}

This can even be nested deeper:

g[x_] := (g[y_] := (g[z_] := z + 4; y - 1); x + 1)

g /@ {1, 2, 3}
{2, 1, 7}

A note regarding syntax

When using this construct for memoization it is often nice to use a named pattern for the entire left-hand-side, because it is:

  • more concise
  • less prone to error (less code to retype)
  • a convenient label for purpose of the construct

All these things are most useful when the definition is rather long.
It would not improve f[x_] := f[x] = . . . but for a contrived example of memoization:

mem : combinations[set_Integer?Positive, choose_Integer?Positive] := 
  mem = set!/(choose! (set - choose)!)

After getting used to this convention seeing mem : will immediately let you know this is a memoized function.

share|improve this answer
2  
Very good points. +1 –  Leonid Shifrin Mar 7 '12 at 6:49
    
You can avoid reinitialization if you wrap the application in an Internal`InheritedBlock like this: Internal`InheritedBlock[{f}, f/@ {3,5,2,3,2,4,3}] –  celtschk Mar 7 '12 at 11:34
    
@celtschk interesting method, although if f were ever used outside InheritedBlock the result could be very frustrating. –  Mr.Wizard Mar 7 '12 at 13:58
    
Well, then encapsulate it, e.g. Module[{dedup},dedup[x_]:=(dedup[x]=Sequence[];x);UnsortedUnion[l_List]:=Intern‌​al`InheritedBlock[{dedup},dedup/@l]] –  celtschk Mar 7 '12 at 15:16
    
@celtschk is there an advantage to that over UnsortedUnion = Module[{f}, f[y_] := (f[y] = Sequence[]; y); f /@ Join@##] & or do you share it as a curiosity? –  Mr.Wizard Mar 8 '12 at 7:42

This is my first reply in this group. So please bear with me if I make any mistake, it would not be intentional, just lack of familiarity with the rules.

Although the replies above mention important aspects, I generally like to view things from alternative perspectives. I'd like to offer a few of those on this question. Understanding is enhanced by viewing the same thing from different angles.

The word "memoization" was already mentioned above by Leonid, you can also think of it as "caching". Consider it as an example of caching data to prevent re-computation. Using the Fibonacci numbers as a simple example, if you have a definition like

fib[n_]:=fib[n]=fib[n-1]+fib[n-2];
fib[0]=1;
fib[1]=1;
fib@5

??fib

then every time you evaluate fib@somethingalreadycomputed M will simply pull the result from memory and not compute it again. This type of construction ensures that you ONLY compute a new result if none is known already for the given argument. In fact, the very fact that the formula is shown at the bottom is exactly how M treats this internally: like a look-up table. You are guaranteed that the order in which ?? (Information) outputs the results is exactly how M looks these things up internally. So the formula is ONLY evaluated if no prior match is found. This is how a typical look-up table works in a software system, this has nothing to do with M. You go through your list sequentially, and at the first match, bail out with the result. That's why it's important to have a look-up table in the proper order, which M ensures us. ?? (Information) outputs exactly the way the look-up table is treated in the kernel.

Compare this with

Clear@fib;
fib[n_]:=fib[n-1]+fib[n-2];
fib[0]=1;
fib[1]=1;
fib@5

??

where the intermediate values are not defined, but have to be computed and re-computed every time you evaluate fib@someInteger. This can be atrociously inefficient for larger n when the function has above-linear complexity.

Another perspective is to liken this to (a particular implementation of) the proxy pattern in object-oriented design. For example, in Java or C# code you frequently find constructs such as

public void displayImage() {
       if (image == null) {
           image = new RealImage(filename);
        } 
        image.displayImage();
    }

If the image doesn't exist, create it, then display it. If it does exist, skip the creation part, and display immediately what we have already in memory. The visitor pattern in OO design also oftentimes works like this. You check for existence, and you base your next decision on the answer to the existence question. This is the most efficient way to ensure that every unique object is only computed once ever.

Another perspective to think of this is as a combination of computation and data. When you compute something with a formula, you are performing a computation. But when you look up something from memory, you are not computing, you are pulling data and return data. This construction is a very convenient way to combine computation and data and let the system decide which one is to be done at execution time. That's actually a very important concept in software design: data vs. computation. We may not think about it this way because it's so easy in M.

Next,

TreeForm@Hold[fib[n_]:=fib[n]=fib[n-1]+fib[n-2]]

tells us that the SetDelayed is evaluated as the outermost function. Thus, the parentheses are

fib[n_]:=(fib[n]=fib[n-1]+fib[n-2]);

Now it's easy to see what happens. Every time you call fib[someInteger], we get the result of an equation (Set). In M the result of an equation (Set) is either the known result (if the evaluated left-hand side is known), or the result of the computation of the right-hand side (if the left-hand side is not known -- yet!). In the latter case it now assigns it to the evaluated left-hand side fib[n] and will stored as such, as downvalues are attached to the left-hand side of their assignments.

share|improve this answer
    
Welcome to Mathematica.SE! It's great to see you contribute here –  Szabolcs Jun 28 '12 at 19:55
    
Don't be shy, this is a fine answer (+1). Hope to see more of you here! –  Sjoerd C. de Vries Jun 28 '12 at 19:58
    
Good one with the QR code... welcome to der Party! –  Yves Klett Mar 9 '13 at 9:06

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.